C#JuniorCoding
Что такое extension methods и как создать свой?
Extension method — статический метод со специальным первым параметром this T, позволяющий синтаксически вызывать его как метод экземпляра без изменения типа. Типичная ловушка: extension на IEnumerable вместо IQueryable тянет все данные из БД в память.
Extension methods в C#
Extension method — статический метод, который синтаксически вызывается как метод экземпляра, не требуя изменения исходного типа. Это позволяет «расширять» sealed-классы, интерфейсы и сторонние типы без наследования.
Как создать extension method
// 1. Статический класс (имя может быть любым)
public static class StringExtensions
{
// 2. Первый параметр — 'this TипРасширяемого'
public static bool IsNullOrWhiteSpace(this string? value)
=> string.IsNullOrWhiteSpace(value);
public static string Truncate(this string value, int maxLength)
{
ArgumentNullException.ThrowIfNull(value);
return value.Length <= maxLength
? value
: value[..maxLength] + "…";
}
// Extension на интерфейс — работает для всех реализаций
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
where T : class
=> source.Where(x => x is not null)!;
}
// Использование
string title = "Senior Backend Engineer at Google";
Console.WriteLine(title.Truncate(20)); // "Senior Backend Engin…"
List<string?> names = ["Alice", null, "Bob", null];
var valid = names.WhereNotNull().ToList(); // ["Alice", "Bob"]
Extension methods на generic-типах и ограничениях
public static class CollectionExtensions
{
// Добавить в коллекцию несколько элементов (аналог Python list.extend)
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
collection.Add(item);
}
// Безопасный FirstOrDefault с дефолтным значением (не null)
public static T FirstOrFallback<T>(this IEnumerable<T> source, T fallback)
=> source.DefaultIfEmpty(fallback).First();
}
// Extension на IQueryable для переиспользуемых фильтров
public static class JobQueryExtensions
{
public static IQueryable<Job> WhereRemote(this IQueryable<Job> query)
=> query.Where(j => j.RemoteType == RemoteType.FullRemote);
public static IQueryable<Job> OrderByFreshness(this IQueryable<Job> query)
=> query.OrderByDescending(j => j.CreatedAt);
}
// Цепочка:
var remoteJobs = db.Jobs
.WhereRemote()
.OrderByFreshness()
.Take(20)
.ToListAsync(ct);
Приоритет и разрешение
- Метод экземпляра всегда имеет приоритет над extension method с той же сигнатурой.
- Extension method видим только при наличии
usingпространства имён, в котором объявлен статический класс. - Компилятор ищет extension methods в текущем пространстве имён, затем в imported namespaces (от ближайшего к дальнему).
Подводные камни
- Null receiver. Extension method может быть вызван на null:
((string)null).IsNullOrWhiteSpace()не бросает NullReferenceException, если метод явно обрабатывает null. Это может удивить — документируйте поведение. - Скрытие за приоритетом. Если в будущей версии библиотеки появится метод экземпляра с той же сигнатурой, он «победит» extension method без предупреждения компилятора — поведение молча изменится.
- IQueryable vs IEnumerable. Extension method на
IEnumerable<T>вызоветAsEnumerable()неявно, если передатьIQueryable<T>— все данные уйдут в память. Создавайте раздельные перегрузки. - Видимость через using. Если extension method не видно — проверьте, добавлен ли нужный
using. Это частая причина ошибки CS1061. - Нельзя переопределить. Extension methods статические — их нельзя переопределить в наследнике. Для полиморфного поведения используйте виртуальные методы или интерфейсы.
- Не злоупотребляйте. Extension methods хороши для утилитарных операций и fluent API, но чрезмерное использование создаёт «скрытые» зависимости и затрудняет навигацию в IDE.
Common mistakes
- Путать extension methods и расширение API без наследования с похожим механизмом из другой версии или платформы.
- Игнорировать runtime-границы C#: lifecycle, DI scope, SQL translation, UI thread или platform API.
- Не обсуждать null/empty/error cases и поведение под нагрузкой.
What the interviewer is testing
- Кандидат объясняет extension methods и расширение API без наследования на конкретном примере, а не только определением.
- Указывает последствия для производительности, тестируемости и поддержки.
- Различает документированное поведение текущего стека и устаревшие практики.