Когда генерируется дженерик-класс конкретного типа - при выполнении программы или во время компиляции?csharp-79

Вопрос о моменте создания специализированных дженерик-типов в C# вызывает много дискуссий. Давайте разберемся детально:

Как работает генерация дженерик-типов

  1. Во время компиляции:

    • Генерируется обобщенный (шаблонный) код для дженерик-типа
    • Создается метаданные для всех возможных параметров типа
    • Происходит статическая проверка типов
  2. Во время выполнения (JIT-компиляция):

    • При первом использовании конкретной специализации (List<int>, List<string> и т.д.)
    • JIT-компилятор создает специализированный машинный код для каждого уникального типа-параметра

Пример процесса

// Обобщенный класс (не создается в бинарнике как готовый код)
public class GenericBox<T> {
    public T Value { get; set; }
}

// При первом использовании в runtime:
var intBox = new GenericBox<int>(); // JIT генерирует специализированный код для int
var stringBox = new GenericBox<string>(); // Затем для string

Важные особенности

  1. Кэширование типов:

    • Сгенерированные специализации кэшируются
    • Повторное использование того же типа не требует новой генерации
  2. Разделение кода:

    • Для ссылочных типов (например, List<string>, List<object>) используется один экземпляр кода
    • Для значимых типов (например, List<int>, List<DateTime>) создаются отдельные реализации
  3. Динамическая генерация:

    • С помощью System.Reflection.Emit можно создавать дженерик-типы полностью во время выполнения

Оптимизации в современных версиях .NET

  1. Tiered JIT-компиляция:

    • Быстрая генерация начального кода
    • Последующая оптимизация "горячих" специализаций
  2. Generic Sharing:

    • Максимальное повторное использование кода
    • Минимизация дублирования

Практические последствия

  1. Размер исполняемого файла:

    • Не увеличивается от количества возможных параметров типа
    • Увеличивается только от фактически используемых специализаций
  2. Производительность:

    • Первое использование типа имеет небольшие накладные расходы
    • Последующие вызовы работают на скорости статических типов
// Измерение времени первой генерации
var watch = System.Diagnostics.Stopwatch.StartNew();
var list = new List<MyCustomType>(); // Включает генерацию кода
watch.Stop();
Console.WriteLine($"First gen time: {watch.ElapsedTicks} ticks");

Резюмируем:

дженерик-классы конкретных типов генерируются во время выполнения при первом использовании каждой уникальной комбинации параметров типа, а не во время компиляции. Это ключевая особенность реализации дженериков в .NET, обеспечивающая баланс между гибкостью и производительностью.