0Pricing
C# Academy · Lekcja

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 return do tworzenia operatorów działających leniwie.
  • Ograniczenia where pozwalają wymagać możliwości takich jak IComparable<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

  1. Definiowanie metod rozszerzających
  2. Rozszerzanie interfejsów i typów generycznych
  3. Rozstrzyganie metod rozszerzających
  4. Projektowanie dobrych rozszerzeń
← Powrót do C# Academy