Rozszerzanie interfejsów i typów generycznych
Dodawaj zachowanie jednocześnie do wielu typów.
Rozszerzanie interfejsów i typów generycznych to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Rozszerzanie interfejsów
Można napisać metodę rozszerzającą, której parametr this jest interfejsem. Każdy typ implementujący ten interfejs natychmiast zyskuje tę metodę. Dokładnie tak LINQ dodaje dziesiątki metod do IEnumerable<T>.
Generyczna metoda rozszerzająca dla IEnumerable
Uczynienie metody generyczną względem T i rozszerzenie za jej pomocą IEnumerable<T> sprawia, że pomocnicza metoda działa z dowolną sekwencją: listami, tablicami, wynikami zapytań i innymi.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static bool IsEmpty<T>(this IEnumerable<T> source)
{
foreach (var item in source) return false;
return true;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
Console.WriteLine(nums.IsEmpty());
Console.WriteLine(new int[0].IsEmpty());
}
}Iterowanie po sekwencji
Wewnątrz metody rozszerzającej parametr jest traktowany jak dowolny obiekt IEnumerable<T>. W tym przykładzie obliczana jest suma bez korzystania z LINQ.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static int SumValues(this IEnumerable<int> source)
{
int total = 0;
foreach (var n in source) total += n;
return total;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 10, 20, 30 };
Console.WriteLine(nums.SumValues());
}
}Zwracanie nowej sekwencji
Metody rozszerzające mogą również zwracać sekwencje. W połączeniu z yield return stają się operatorami działającymi leniwie, podobnymi do operatorów LINQ.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
bool take = true;
foreach (var item in source)
{
if (take) yield return item;
take = !take;
}
}
}
public class Program
{
public static void Main()
{
var letters = new List<string> { "a", "b", "c", "d", "e" };
foreach (var l in letters.EveryOther())
Console.WriteLine(l);
}
}Ograniczenia typów generycznych
Parametr generyczny można ograniczyć, aby wymagał określonych możliwości. W tym przypadku where T : IComparable<T> pozwala porównywać elementy w celu znalezienia wartości maksymalnej.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static T MaxItem<T>(this IEnumerable<T> source) where T : IComparable<T>
{
bool started = false;
T best = default;
foreach (var item in source)
{
if (!started || item.CompareTo(best) > 0) { best = item; started = true; }
}
return best;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 3, 9, 1, 7 };
Console.WriteLine(nums.MaxItem());
}
}Rozszerzanie własnego interfejsu
Własne interfejsy również mogą na tym skorzystać. Należy zdefiniować interfejs, a następnie dołączyć wspólne zachowanie za pomocą metod rozszerzających, zamiast powielać je w każdej klasie implementującej interfejs.
using System;
public interface INamed { string Name { get; } }
public class Dog : INamed { public string Name => "Rex"; }
public static class NamedExtensions
{
public static string Greeting(this INamed n) => "Hello, " + n.Name;
}
public class Program
{
public static void Main()
{
var d = new Dog();
Console.WriteLine(d.Greeting());
}
}Wiele parametrów typów
Metody rozszerzające mogą być generyczne względem kilku parametrów typów. Ten pomocniczy operator projekcji odwzorowuje sekwencję za pomocą funkcji, podobnie jak operator LINQ Select.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<TResult> Map<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, TResult> f)
{
foreach (var item in source) yield return f(item);
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
foreach (var s in nums.Map(n => "#" + n))
Console.WriteLine(s);
}
}Łączenie z wbudowanym LINQ
Własne metody rozszerzające współistnieją z LINQ. Można połączyć własny operator ze standardowymi operatorami w jednym płynnym potoku.
using System;
using System.Collections.Generic;
using System.Linq;
public static class EnumerableExtensions
{
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
bool take = true;
foreach (var item in source) { if (take) yield return item; take = !take; }
}
}
public class Program
{
public static void Main()
{
var nums = Enumerable.Range(1, 10);
var result = nums.Where(n => n > 2).EveryOther().ToList();
Console.WriteLine(string.Join(", ", result));
}
}Rozszerzanie IEnumerable obejmuje wiele typów
Ponieważ tablice, listy, słowniki, zbiory haszujące i wyniki zapytań implementują interfejs IEnumerable<T>, jedno rozszerzenie obejmuje je wszystkie.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static int CountItems<T>(this IEnumerable<T> source)
{
int n = 0;
foreach (var item in source) n++;
return n;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(new[] { 1, 2, 3 }.CountItems());
Console.WriteLine(new HashSet<string> { "a", "b" }.CountItems());
}
}Dlaczego ten wzorzec dobrze się skaluje
Rozszerzenia interfejsów zapewniają polimorficzne metody pomocnicze bez dziedziczenia. Logikę należy napisać raz dla interfejsu, a każdy jego implementator — obecny i przyszły — otrzyma ją bez dodatkowego kodu.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static string JoinWith<T>(this IEnumerable<T> source, string sep)
{
return string.Join(sep, source);
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
Console.WriteLine(nums.JoinWith(" -> "));
}
}Wypróbuj samodzielnie
Proszę zbudować mały generyczny operator dla IEnumerable<T> i połączyć go z LINQ. Proszę zwrócić uwagę, że działa on z dowolnym typem sekwencji.
using System;
using System.Collections.Generic;
using System.Linq;
public static class EnumerableExtensions
{
public static IEnumerable<T> WithoutDuplicatesByKey<T, TKey>(
this IEnumerable<T> source, Func<T, TKey> keySelector)
{
var seen = new HashSet<TKey>();
foreach (var item in source)
if (seen.Add(keySelector(item))) yield return item;
}
}
public class Program
{
public static void Main()
{
var words = new[] { "apple", "avocado", "banana", "cherry", "blueberry" };
var firstPerLetter = words.WithoutDuplicatesByKey(w => w[0]);
Console.WriteLine(string.Join(", ", firstPerLetter));
}
}Szybkie sprawdzenie
Proszę zastanowić się, jak deklaruje się rozszerzenia w stylu LINQ.
Podsumowanie
Rozszerzanie interfejsów, szczególnie IEnumerable<T>, stanowi podstawę programowania w stylu LINQ.
- Metoda powinna być generyczna, a interfejs należy wykorzystać jako typ
this. - Należy używać
yield returndo tworzenia operatorów działających leniwie. - Ograniczenia
wherepozwalają wymagać możliwości takich jakIComparable<T>. - Jedno rozszerzenie interfejsu obejmuje wszystkich jego implementatorów, także przyszłych.
Często zadawane pytania
Czy lekcja „Rozszerzanie interfejsów i typów generycznych” jest bezpłatna?
Tak — pełny tekst „Rozszerzanie interfejsów i typów generycznych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Rozszerzanie interfejsów i typów generycznych”?
Dodawaj zachowanie jednocześnie do wielu typów. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Rozszerzanie interfejsów i typów generycznych”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Definiowanie metod rozszerzających
- Rozszerzanie interfejsów i typów generycznych
- Rozstrzyganie metod rozszerzających
- Projektowanie dobrych rozszerzeń