Generics: обобщённые типы и методы
↑ 1.5 Коллекции и основы LINQ · ← Предыдущая · → Следующая
Зачем это на собесе
generics — основа типобезопасного переиспользуемого кода: репозитории, Result<T>, обобщённые хендлеры, клиенты. Здесь база, а продвинутое (вариантность, static abstract) — во 2-м этапе.
Объяснение
Обобщённые типы
public sealed class Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }
private Result(bool ok, T? value, string? error) => (IsSuccess, Value, Error) = (ok, value, error);
public static Result<T> Ok(T value) => new(true, value, null);
public static Result<T> Fail(string error) => new(false, default, error);
}
Result<PatientDto> r = Result<PatientDto>.Ok(dto);Без generics пришлось бы использовать object: потеря типобезопасности, приведения и boxing для value types.
Обобщённые методы и вывод типов
public static T Require<T>(T? value, string name) where T : class =>
value ?? throw new ArgumentNullException(name);
var patient = Require(found, nameof(found)); // T выведен компиляторомОграничения (constraints)
public interface IEntity { Guid Id { get; } }
public class Repository<T>(DbContext db) where T : class, IEntity // ссылочный тип и реализует IEntity
{
public Task<T?> GetAsync(Guid id, CancellationToken ct) =>
db.Set<T>().FirstOrDefaultAsync(e => e.Id == id, ct);
}where T : class/class?— ссылочный тип.where T : struct— значимый тип (не nullable).where T : notnull— не допускающий null.where T : new()— есть публичный конструктор без параметров.where T : BaseType,where T : IInterface.where T : unmanaged— без ссылок внутри (для низкоуровневой работы с памятью).where T : Enum,where T : Delegate.
Как generics работают в CLR
- Для value types JIT создаёт отдельный машинный код для каждого типа (
List<int>,List<double>): без боксинга, максимальная скорость. - Для reference types код общий (все ссылки одного размера).
- В отличие от Java, generics в .NET не стираются: тип доступен в рантайме (
typeof(T), рефлексия).
default(T)
T? FirstOrNothing<T>(IEnumerable<T> items) => items.Any() ? items.First() : default; // null или 0Статические поля в generic-типе
У каждого закрытого типа свои: Cache<Patient>.Items и Cache<Encounter>.Items — разные. Иногда это используют как типизированный кэш.
Generic-интерфейсы в DI
public interface IHandler<TRequest, TResponse> { Task<TResponse> HandleAsync(TRequest req, CancellationToken ct); }
builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); // открытый genericНюансы и подводные камни
- Generic-репозиторий «на всё» часто становится бесполезной обёрткой над
DbSet: специфичные запросы всё равно пишут отдельно. List<Derived>нельзя присвоитьList<Base>(инвариантность), аIEnumerable<Derived>вIEnumerable<Base>— можно (ковариантность). Подробнее — во втором этапе.- Сравнение значений типа T через
==не компилируется без ограничений. ИспользуйтеEqualityComparer<T>.Default.Equals(a, b). - Слишком общие generic-API с кучей параметров типов трудно читать. Generics — для реального переиспользования.
Тестирование
[Fact]
public void Result_holds_value_on_success()
{
var r = Result<int>.Ok(42);
r.IsSuccess.Should().BeTrue();
r.Value.Should().Be(42);
}Вопросы с ответами
Зачем нужны generics?
Типобезопасное переиспользование кода для разных типов без приведений и боксинга. Ошибки типов ловятся компилятором.
Какие ограничения можно наложить на параметр типа?
class, struct, notnull, unmanaged, new(), базовый класс, интерфейс, Enum, Delegate и их комбинации.
Чем generics в .NET отличаются от Java?
В .NET они реифицированы: информация о типе есть в рантайме, для value types генерируется отдельный код без боксинга. В Java типы стираются при компиляции.
Что вернёт default(T)?
null для ссылочных типов, нулевое значение для значимых (0, false, структура с нулевыми полями).
Связанные темы
- Предыдущая: Интерфейсы коллекций: IEnumerable, ICollection, IList, IReadOnly · Следующая: LINQ: основные операторы
- Generics глубоко: Generics глубоко: constraints, вариантность, static abstract
- Result pattern: Result pattern и обработка ошибок без исключений
- Repository поверх EF: Repository и Unit of Work поверх EF: нужно ли
- Generics в TypeScript: Generics