Можно ли в C# объявить обобщенный класс (generic) с более чем одним параметром типа?

Ответ

Да, в C# можно использовать произвольное количество универсальных параметров типа (type parameters), разделяя их запятыми в угловых скобках <>. Это применяется в классах, структурах, интерфейсах и методах.

Примеры:

  1. Класс с двумя параметрами (аналог KeyValuePair<TKey, TValue>):

    public class Result<TData, TError>
    {
        public bool IsSuccess { get; init; }
        public TData? Data { get; init; }
        public TError? Error { get; init; }
    
        public static Result<TData, TError> Success(TData data) => 
            new() { IsSuccess = true, Data = data };
        public static Result<TData, TError> Failure(TError error) => 
            new() { IsSuccess = false, Error = error };
    }
    // Использование:
    Result<Order, string> operationResult = Result<Order, string>.Success(new Order());
  2. Метод с несколькими параметрами типа:

    public TOutput Transform<TInput, TOutput>(TInput input, Func<TInput, TOutput> mapper)
    {
        return mapper(input);
    }
    // Использование:
    int number = 42;
    string text = Transform<int, string>(number, n => n.ToString());
  3. Интерфейс с ограничениями (constraints) для разных параметров:

    public interface IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>
    {
        TEntity? GetById(TKey id);
        void Add(TEntity entity);
    }
    // Реализация:
    public class UserRepository : IRepository<User, int> { /* ... */ }

Рекомендации:

  • Именуйте параметры типа понятно (например, TKey, TValue, TResult), особенно когда их много.
  • Используйте ограничения (where), чтобы сделать код более типобезопасным и читаемым.
  • Хотя формальных ограничений нет, чрезмерное количество параметров (более 3-4) часто сигнализирует о сложности дизайна, который стоит пересмотреть.

Ответ 18+ 🔞

Смотри, в C# тебе никто не запрещает навешивать на свои классы, методы и прочую хуету сколько угодно дженерик-параметров. Просто пишешь их через запятую в этих самых угловых скобках <>, и всё, поехали. Работает везде — в классах, структурах, интерфейсах, методах.

Вот, смотри как это выглядит на практике:

  1. Класс с двумя типами (типа самодельный KeyValuePair):

    public class Result<TData, TError>
    {
        public bool IsSuccess { get; init; }
        public TData? Data { get; init; }
        public TError? Error { get; init; }
    
        public static Result<TData, TError> Success(TData data) => 
            new() { IsSuccess = true, Data = data };
        public static Result<TData, TError> Failure(TError error) => 
            new() { IsSuccess = false, Error = error };
    }
    // Используем:
    Result<Order, string> operationResult = Result<Order, string>.Success(new Order());

    Ну, тут логично же: один тип для удачного результата, другой — для описания косяка. Удобно, ёпта.

  2. Метод, который тоже может быть с несколькими типами:

    public TOutput Transform<TInput, TOutput>(TInput input, Func<TInput, TOutput> mapper)
    {
        return mapper(input);
    }
    // Ну и вызываем:
    int number = 42;
    string text = Transform<int, string>(number, n => n.ToString());

    Вообще красота — сам контролируешь, что на входе и что на выходе, компилятор потом не придерётся.

  3. А вот интерфейс, где на каждый тип можно свои ограничения навесить:

    public interface IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>
    {
        TEntity? GetById(TKey id);
        void Add(TEntity entity);
    }
    // Пишем реализацию:
    public class UserRepository : IRepository<User, int> { /* ... */ }

    Видишь? Говорим: TEntity должен быть классом и реализовывать IEntity<TKey>. А TKey уже сам по себе, какой скажут. Чётко.

А теперь, блядь, важные моменты, чтобы не выстрелить себе в ногу:

  • Имена давай нормальные. Не T1, T2, T3 — это пиздец как потом разбираться. Пиши TKey, TValue, TResult, чтобы было понятно, кто за что отвечает. Особенно когда их больше двух.
  • Ограничения (where) — твой друг. Они не для галочки, они делают код безопаснее и понятнее. Компилятор заранее отсечёт тупые попытки подсунуть не тот тип.
  • И главное, не ебенись. Формально ограничений нет, можешь хоть 10 параметров написать. Но если у тебя их больше 3-4 — это уже не код, а какая-то абстрактная хуйня. Скорее всего, дизайн твоего класса или метода — говно, и его надо пересмотреть. Серьёзно, остановись и подумай, не усложняешь ли ты всё просто так.

В общем, инструмент мощный, но как любой инструмент — можно и по пальцам себе им въебать, если использовать как попало. Думай головой, и всё будет пучком.