Расширение интерфейсов и обобщённых типов
Добавляйте поведение сразу для множества типов.
«Расширение интерфейсов и обобщённых типов» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Определение методов расширения
- Расширение интерфейсов и обобщённых типов
- Выбор метода расширения
- Проектирование хороших расширений