← Назад к вопросам

Что такое дженерики?

2.2 Middle🔥 251 комментариев
#Коллекции и структуры данных#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое дженерики (Generics) в C#?

Дженерики — это мощный механизм в C#, который позволяет создавать типобезопасные, переиспользуемые и эффективные классы, структуры, интерфейсы и методы, работающие с различными типами данных, не привязываясь к конкретному типу на этапе написания кода. Они были введены в C# 2.0 и стали фундаментальной частью языка, устраняя недостатки подходов с использованием типа object или специализированных классов.

Основная идея и преимущества

Дженерики позволяют параметризовать типы, подобно тому, как методы параметризуются значениями. Вместо жесткого указания типа (например, int или string), вы объявляете параметр типа (обычно обозначаемый как T, TKey, TValue и т.д.), который будет заменен конкретным типом во время использования.

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

  • Типобезопасность: Компилятор проверяет соответствие типов на этапе компиляции, предотвращая ошибки приведения типов (InvalidCastException), которые были характерны для подходов с object.
  • Производительность: Устраняется необходимость упаковки (boxing) для значимых типов (value types), так как код генерируется с учетом конкретного типа. Это значительно улучшает производительность при работе, например, с коллекциями примитивных типов.
  • Переиспользуемость кода: Один обобщенный класс или метод может использоваться с множеством различных типов без дублирования логики.
  • Читаемость и поддержка: Код становится более выразительным и ясным, так как намерения разработчика (работа с обобщенным типом) явно указаны в объявлении.

Примеры использования

1. Обобщенный класс (Generic Class)

Самый распространенный пример — коллекции из пространства имен System.Collections.Generic.

// Объявление обобщенного класса
public class Repository<T>
{
    private List<T> _items = new List<T>();

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

    public T GetById(int index)
    {
        return _items[index];
    }
}

// Использование с разными типами
Repository<string> stringRepo = new Repository<string>();
stringRepo.Add("Hello");
string value = stringRepo.GetById(0); // Тип возвращаемого значения - string

Repository<int> intRepo = new Repository<int>();
intRepo.Add(42);
int number = intRepo.GetById(0); // Тип возвращаемого значения - int
// Компилятор гарантирует типобезопасность: нельзя добавить string в intRepo.

2. Обобщенный метод (Generic Method)

Методы также могут быть обобщенными, даже если содержащий их класс таковым не является.

public class Utility
{
    // Обобщенный метод для обмена значений
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
}

// Использование
int x = 5, y = 10;
Utility.Swap(ref x, ref y); // Компилятор выводит тип T как int

string s1 = "foo", s2 = "bar";
Utility.Swap(ref s1, ref s2); // T выводится как string

3. Ограничения параметров типа (Constraints)

Часто требуется гарантировать, что параметр типа будет обладать определенными характеристиками. Для этого используются ограничения (where).

// T должен быть классом (ссылочным типом) и реализовывать интерфейс IComparable
public class Sorter<T> where T : class, IComparable<T>
{
    public void Sort(List<T> list)
    {
        list.Sort(); // Метод Sort доступен благодаря IComparable<T>
    }
}

// T должен иметь конструктор без параметров
public T CreateInstance<T>() where T : new()
{
    return new T(); // Возможно только с ограничением new()
}

// T должен быть типом значения (структурой)
public struct Nullable<T> where T : struct
{
    // Реализация nullable-типа для value types
}

Как это работает на уровне CLR?

При компиляции обобщенного типа CLR (Common Language Runtime) создает специализированную сконструированную форму (constructed form) для каждого уникального типа-аргумента, используемого в программе. Однако для ссылочных типов (например, Repository<string> и Repository<Stream>) используется разделение кода (code sharing), так как они имеют одинаковое внутреннее представление (указатели). Для значимых типов (например, Repository<int> и Repository<long>) создаются отдельные реализации, что исключает упаковку и оптимизирует выполнение.

Заключение

Дженерики — это неотъемлемая часть современного C#, лежащая в основе типобезопасных коллекций (List<T>, Dictionary<TKey, TValue>), методов LINQ, механизмов async/await (Task<T>) и многих других аспектов платформы .NET. Их использование является признаком качественного кода, так как оно напрямую способствует повышению надежности, производительности и сопровождаемости приложений. Понимание дженериков — обязательное требование для backend-разработчика на C#, работающего с сложными системами и данными.

Что такое дженерики? | PrepBro