Extension methods и fluent API
↑ 2.1 C♯: продвинутый язык · ← Предыдущая · → Следующая
Зачем это на собесе
extension-методы — основа LINQ и всей конфигурации ASP.NET Core (
services.AddXxx(),app.UseXxx()). В проекте общие пакеты (core/extensions,http_shared/DI) подключаются именно так.
Объяснение
Extension-метод — статический метод в статическом классе, первый параметр помечен this. Вызывается как метод экземпляра.
public static class StringExtensions
{
public static string? NullIfEmpty(this string? s) => string.IsNullOrWhiteSpace(s) ? null : s.Trim();
public static string Truncate(this string s, int max) => s.Length <= max ? s : s[..max] + "…";
}
var middle = dto.MiddleName.NullIfEmpty(); // компилятор превращает в StringExtensions.NullIfEmpty(dto.MiddleName)- Не имеют доступа к
private-членам типа, только к публичному API. - Не переопределяют методы: если у типа есть метод с той же сигнатурой, вызовется метод экземпляра.
- Разрешаются статически по типу переменной и подключённым
using. - Можно вызвать на
null(это обычный статический вызов), поэтому проверяйте аргумент.
Расширения для DI и pipeline (главный практический кейс)
// пакет core/http_shared — одна строка подключения во всех сервисах
public static class HttpSharedExtensions
{
public static IServiceCollection AddHttpShared(this IServiceCollection services, IConfiguration cfg)
{
services.AddHttpContextAccessor();
services.AddScoped<IUserContext, UserContext>();
services.AddExceptionHandler<GlobalExceptionHandler>();
services.AddProblemDetails();
services.Configure<KeycloakOptions>(cfg.GetSection("Keycloak"));
return services; // возвращаем для цепочки (fluent)
}
public static IApplicationBuilder UseHttpShared(this IApplicationBuilder app) =>
app.UseExceptionHandler().UseMiddleware<CorrelationIdMiddleware>();
}
// Program.cs сервиса
builder.Services.AddHttpShared(builder.Configuration).AddTracing(builder.Configuration);
app.UseHttpShared();Расширения для интерфейсов и generic
public static class QueryableExtensions
{
public static IQueryable<T> Page<T>(this IQueryable<T> q, int page, int size) =>
q.Skip((page - 1) * size).Take(size);
public static IQueryable<T> WhereIf<T>(this IQueryable<T> q, bool condition, Expression<Func<T, bool>> pred) =>
condition ? q.Where(pred) : q;
}
var result = await db.Patients
.WhereIf(filter.City is not null, p => p.City == filter.City)
.OrderBy(p => p.LastName)
.Page(filter.Page, 50)
.ToListAsync(ct);Fluent API
Цепочка вызовов, где каждый метод возвращает объект для продолжения: builder.Services.AddX().AddY(), EF Core modelBuilder.Entity<T>().HasKey(...).HasIndex(...), FluentValidation RuleFor(x => x.Name).NotEmpty().MaximumLength(100), построители отчётов.
C# 14: extension members
Новый синтаксис блоков extension(Type t) { ... } — можно объявлять extension-свойства и статические члены, а не только методы.
Нюансы и подводные камни
- Экстеншены на
objectилиstringзасоряют IntelliSense по всему проекту. Держите их в узких namespace. - Бизнес-логика в extension-методах плохо тестируется и прячется от DI. Место экстеншенов — утилиты и конфигурация.
- Конфликт имён двух экстеншенов из разных
using→ ошибка неоднозначности. - Extension-метод над
IEnumerable<T>иIQueryable<T>с одинаковым именем: выбор зависит от статического типа. Легко случайно уйти в память.
Тестирование
[Theory]
[InlineData(" ", null)]
[InlineData(" Иван ", "Иван")]
public void NullIfEmpty_normalizes(string input, string? expected) =>
input.NullIfEmpty().Should().Be(expected);Вопросы с ответами
Что такое extension-метод и как он работает?
Статический метод статического класса с this у первого параметра. Компилятор превращает вызов obj.Method() в Class.Method(obj). Доступа к приватным членам нет.
Что вызовется, если у типа есть метод с такой же сигнатурой, как у extension-метода?
Метод экземпляра: он имеет приоритет. Extension-метод используется, только если подходящего метода экземпляра нет.
Можно ли вызвать extension-метод на null?
Да, это обычный статический вызов, и исключения при вызове не будет. Поэтому внутри нужно проверять аргумент.
Как организовать подключение общей библиотеки в десятках микросервисов?
Extension-методы AddXxx для IServiceCollection и UseXxx для IApplicationBuilder в общем пакете. Сервис подключает всё одной строкой, а конфигурация и регистрация инкапсулированы в библиотеке.
Связанные темы
- Предыдущая: Generics глубоко: constraints, вариантность, static abstract · Следующая: Records, init, with, required, primary constructors
- Организация регистраций в DI: Организация регистраций: extension-методы, модули
- LINQ глубоко: LINQ глубоко: отложенное выполнение, IEnumerable и IQueryable, expression trees
- Общий код между сервисами: Общий код между сервисами: shared kernel, NuGet-пакеты, базовые образы
- Паттерн Builder: Порождающие паттерны: Factory Method, Abstract Factory, Builder, Singleton (фабрика отчётов)