0Pricing
C# Academy · Урок

Расширение интерфейсов и обобщённых типов

Добавляйте поведение сразу для множества типов.

«Расширение интерфейсов и обобщённых типов» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Расширение интерфейсов

Можно написать метод расширения, у которого параметр this имеет тип интерфейс. Любой тип, реализующий этот интерфейс, сразу получает такой метод. Именно так LINQ добавляет десятки методов к IEnumerable<T>.

Обобщённое расширение для IEnumerable

Если сделать метод обобщённым по типу T и расширить IEnumerable<T>, вспомогательный метод будет работать с любой последовательностью: списками, массивами, результатами запросов и другими источниками.

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());
    }
}

Итерация по последовательности

Внутри метода расширения параметр обрабатывается как любой IEnumerable<T>. Здесь вычисляется сумма без использования 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());
    }
}

Возврат новой последовательности

Методы расширения могут также возвращать последовательности. В сочетании с yield return они становятся ленивыми операторами в стиле 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);
    }
}

Ограничения обобщённых типов

Вы можете ограничить обобщённый параметр, потребовав от него определённых возможностей. Здесь where T : IComparable<T> позволяет сравнивать элементы и находить максимальный.

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());
    }
}

Расширение пользовательского интерфейса

Ваши собственные интерфейсы тоже получают преимущества. Определите интерфейс, а затем добавьте общее поведение с помощью методов расширения вместо его дублирования в каждом реализующем типе.

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());
    }
}

Несколько параметров типов

Методы расширения могут быть обобщёнными сразу по нескольким параметрам типов. Этот вспомогательный метод проекции преобразует последовательность с помощью функции, подобно Select из LINQ.

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);
    }
}

Сочетание со встроенным LINQ

Ваши методы расширения сосуществуют с LINQ. Вы можете объединить собственный оператор со стандартными операторами в едином последовательном конвейере вызовов.

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));
    }
}

Расширение IEnumerable охватывает множество типов

Поскольку массивы, списки, словари, хеш-наборы и результаты запросов реализуют IEnumerable<T>, одно расширение сразу подходит для всех них.

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());
    }
}

Почему этот шаблон хорошо масштабируется

Расширения интерфейсов позволяют создавать полиморфные вспомогательные методы без наследования. Напишите логику один раз для интерфейса — и каждый реализующий его тип, существующий сейчас или появившийся в будущем, получит её бесплатно.

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(" -> "));
    }
}

Попробуйте сами

Создайте небольшой обобщённый оператор для IEnumerable<T> и объедините его с LINQ. Обратите внимание, как он работает с последовательностью любого типа.

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));
    }
}

Быстрая проверка

Подумайте, как объявляются методы расширения в стиле LINQ.

Итоги

Расширение интерфейсов, особенно IEnumerable<T>, — основа программирования в стиле LINQ.

  • Сделайте метод обобщённым и используйте интерфейс как тип this.
  • Используйте yield return для построения ленивых операторов.
  • Применяйте ограничения where, чтобы требовать такие возможности, как IComparable<T>.
  • Одно расширение интерфейса подходит всем реализующим его типам, включая будущие.

Часто задаваемые вопросы

Урок «Расширение интерфейсов и обобщённых типов» бесплатный?

Да — полный текст урока «Расширение интерфейсов и обобщённых типов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Расширение интерфейсов и обобщённых типов»?

Добавляйте поведение сразу для множества типов. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Расширение интерфейсов и обобщённых типов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Определение методов расширения
  2. Расширение интерфейсов и обобщённых типов
  3. Выбор метода расширения
  4. Проектирование хороших расширений
← Назад к C# Academy