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 без наследования на конкретном примере, а не только определением.
  • Указывает последствия для производительности, тестируемости и поддержки.
  • Различает документированное поведение текущего стека и устаревшие практики.

Sources

Related topics