Какие ограничения существуют при создании собственного generic-типа в C#?

«Какие ограничения существуют при создании собственного generic-типа в C#?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

При создании generic-типов в C# существуют следующие ключевые ограничения и правила:

1. Ограничения на наследование:

  • Нельзя наследовать generic-класс от параметра типа: class MyClass<T> : T {} — приведёт к ошибке компиляции.

2. Ограничение new():

  • Если указано where T : new(), тип T обязан иметь публичный конструктор без параметров.

3. Ограничения на категорию типа:

   where T : struct  // T должен быть типом значения (кроме Nullable<T>)
   where T : class   // T должен быть ссылочным типом
   where T : notnull // T должен быть не-nullable типом (C# 8.0+)

4. Ограничения на интерфейсы и базовые классы:

  • Можно требовать реализацию нескольких интерфейсов и/или одного конкретного класса.
    where T : BaseEntity, IComparable<T>, new()

5. Запрещённые ограничения:

  • Нельзя напрямую указывать enum, delegate или тип массива как ограничение. Для enum можно использовать where T : struct, Enum (C# 7.3+).

6. Ковариантность и контравариантность (in/out):

  • Можно объявить только в интерфейсах и делегатах, и только для ссылочных типов.

Практический пример с ограничениями:

public class DataRepository<T> where T : class, IEntity, new()
{
    public T CreateEntity()
    {
        return new T(); // Корректно, так как есть ограничение new()
    }

    public void Save(T entity) where T : IEntity
    {
        // entity гарантированно реализует IEntity
        entity.Id = GenerateId();
    }
}

Использование ограничений повышает безопасность типов и позволяет компилятору разрешать вызовы определённых методов или конструкторов.