Что такое дженерики? Какие проблемы они решают?csharp-31

Определение дженериков

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

// Обобщенный (generic) класс
public class List<T>  // T - параметр типа
{
    public void Add(T item) { /* ... */ }
    public T Get(int index) { /* ... */ }
}

// Использование с конкретным типом
List<string> stringList = new List<string>();
stringList.Add("Hello");

Проблемы, которые решают дженерики

1. Проблема безопасности типов

Без дженериков (использование object):

ArrayList list = new ArrayList();
list.Add(1);       // int
list.Add("text");  // string - допустимо, но опасно

int num = (int)list[1]; // Runtime ошибка - InvalidCastException

С дженериками:

List<int> intList = new List<int>();
intList.Add(1);
intList.Add("text"); // Ошибка КОМПИЛЯЦИИ - типобезопасность

2. Проблема производительности

Без дженериков (упаковка/распаковка для value types):

ArrayList list = new ArrayList();
list.Add(42);               // Boxing (упаковка в object)
int value = (int)list[0];   // Unboxing (распаковка)

С дженериками (нет упаковки):

List<int> list = new List<int>();
list.Add(42);               // Нет boxing'а
int value = list[0];        // Нет unboxing'а

3. Проблема дублирования кода

Без дженериков:

public class IntStack { public void Push(int item) { /* ... */ } }
public class StringStack { public void Push(string item) { /* ... */ } }
// Для каждого типа нужен отдельный класс!

С дженериками:

public class Stack<T>
{
    public void Push(T item) { /* ... */ }
}
// Один класс для всех типов
Stack<int> intStack = new Stack<int>();
Stack<string> stringStack = new Stack<string>();

Основные преимущества дженериков

  1. Повторное использование кода: Одна реализация для разных типов
  2. Безопасность типов: Ошибки обнаруживаются на этапе компиляции
  3. Производительность: Отсутствие boxing/unboxing для value types
  4. Читаемость кода: Явное указание типов делает код понятнее

Где применяются дженерики?

  1. Коллекции (List, Dictionary<TKey,TValue> и т.д.)
  2. Интерфейсы (IEnumerable, IComparable)
  3. Методы:
    public T Max<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) > 0 ? a : b;
    }
    
  4. Делегаты (Action, Func)

Ограничения типов

Можно накладывать ограничения на generic-параметры:

public class Repository<T> where T : class, IEntity, new()
{
    // T должен быть классом, реализовывать IEntity и иметь конструктор
}

Основные виды ограничений:

  • where T : struct - value type
  • where T : class - reference type
  • where T : new() - имеет конструктор по умолчанию
  • where T : BaseClass - наследует BaseClass
  • where T : ISomeInterface - реализует интерфейс

Ковариантность и контравариантность

Дженерики поддерживают вариативность в интерфейсах и делегатах:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // Ковариантность (out)

Action<object> actObj = o => Console.WriteLine(o);
Action<string> actStr = actObj; // Контравариантность (in)

Резюмируем:


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