Что такое обобщения (Generics) в C#

«Что такое обобщения (Generics) в C#» — вопрос из категории C# Core, который задают на 32% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Обобщения (Generics) — это механизм в C#, позволяющий создавать типобезопасные классы, структуры, интерфейсы и методы, которые работают с данными произвольного типа, указанного во время использования, а не во время объявления. Это устраняет необходимость приведения типов (casting) и проверок во время выполнения, перенося эти проверки на этап компиляции.

Основная идея: Параметризация типов. Вы пишете алгоритм или структуру данных один раз, но указываете конкретный тип, с которым будете работать, позже.

Базовый пример: обобщенный класс Repository<T>

// Объявление обобщенного класса с параметром типа T
public class Repository<T> where T : class, IEntity, new() // Ограничения (constraints)
{
    private readonly List<T> _items = new();

    public void Add(T entity)
    {
        _items.Add(entity);
    }

    public T GetById(int id)
    {
        // Компилятор знает, что T имеет свойство Id (благодаря ограничению IEntity)
        return _items.FirstOrDefault(e => e.Id == id);
    }

    public T CreateNew()
    {
        return new T(); // Возможно благодаря ограничению 'new()'
    }
}

// Использование с конкретными типами
var userRepo = new Repository<User>(); // T = User
userRepo.Add(new User { Id = 1, Name = "Alice" });
User foundUser = userRepo.GetById(1); // Без приведения типа! Тип возврата - User.

var productRepo = new Repository<Product>(); // T = Product
Product newProduct = productRepo.CreateNew();

Обобщенные методы:

// Метод, работающий с массивом любого типа
public static T FindMax<T>(T[] array) where T : IComparable<T>
{
    if (array == null || array.Length == 0)
        throw new ArgumentException("Array is empty or null");

    T max = array[0];
    for (int i = 1; i < array.Length; i++)
    {
        // Сравнение возможно благодаря IComparable<T>
        if (array[i].CompareTo(max) > 0)
            max = array[i];
    }
    return max; // Тип возврата - T
}

// Вызов
int maxInt = FindMax(new[] { 1, 5, 3, 9, 2 }); // T выводится как int
string maxString = FindMax(new[] { "apple", "zebra", "banana" }); // T = string

Ключевые преимущества:

  1. Безопасность типов (Type Safety): Компилятор проверяет соответствие типов. Исключаются ошибки InvalidCastException во время выполнения.

    // Без Generics (старый ArrayList) - ОПАСНО
    ArrayList list = new ArrayList();
    list.Add(42); // boxed to object
    list.Add("oops"); // тоже object
    int num = (int)list[1]; // Runtime InvalidCastException!
    
    // С Generics (List<T>) - БЕЗОПАСНО
    List<int> safeList = new List<int>();
    safeList.Add(42);
    // safeList.Add("oops"); // ОШИБКА КОМПИЛЯЦИИ: нельзя добавить string в List<int>
    int safeNum = safeList[0]; // Приведение не требуется
  2. Производительность:

    • Для значимых типов (value types) исключаются операции упаковки (boxing) и распаковки (unboxing), которые требуют выделения памяти в куче и снижают производительность.
    • Код для конкретных типов генерируется во время JIT-компиляции, что приводит к высокооптимизированному машинному коду.
  3. Повторное использование кода и читаемость: Один обобщенный алгоритм (например, сортировка, поиск) или структура данных (список, словарь) может использоваться с любым типом. Код становится чище и понятнее, так как исчезают приведения типов и object.

Ограничения (Constraints): Позволяют указать требования к параметру типа T.

public class Processor<T> where T : 
    class,        // T должен быть ссылочным типом
    IDisposable,  // T должен реализовывать интерфейс IDisposable
    new()         // T должен иметь публичный конструктор без параметров
{
    public void Process()
    {
        T item = new T(); // Можно создать экземпляр
        using (item)      // Можно использовать в using, т.к. IDisposable
        {
            // ... работа с item
        }
    }
}

Важные встроенные обобщенные типы в .NET: List<T>, Dictionary<TKey, TValue>, Nullable<T>, Task<TResult>, все интерфейсы коллекций (IEnumerable<T>, ICollection<T>).

Видео-ответы