Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

4 года назад
Открыть в
День 1407. #ЗаметкиНаПолях #TipsAndTricks Современные Приёмы в C#. Часть 3. Начало Часть 1 Часть 2 Компиляция Обобщённого Кода Шаблоны C++ Шаблоны C++ (и дженерики) являются формами полиморфных функций. Вы определяете один тип (или метод), который принимает параметр типа и может изменить своё поведение на основе переданного типа. Это просто конструкция времени компиляции. Они указывают компилятору, как генерировать код для типа/метода шаблона. А компилятор выполняет преобразование, называемое мономорфизацией: для каждого аргумента шаблона, фактически переданного типу/методу, компилятор создаёт новую копию типа/метода. Обобщения в С# В С# это конструкция времени выполнения: компилятор фактически выводит сам обобщённый тип/метод в IL (промежуточный язык). Во время выполнения реализация обобщённого типа/метода распределяется между обобщёнными аргументами. Другими словами, обобщения в C# не подвергаются мономорфизации для ссылочных типов. Существует только одна копия реализации типа/метода, которая является общей для всех. Однако для типов-значений обобщения подвергаются мономорфизации! Это имеет смысл: если метод Something<T> определяет значение локальной переменной T, компилятору необходимо знать, насколько велико это значение. Размер ссылки одинаков независимо от типа, на который она ссылается, но размер типа-значения может различаться. Обобщения и ограниченные типы значений Мономорфизация наиболее полезна, если ограничиваете обобщения определённым интерфейсом. Простой и бесполезный пример:
interface ISample
{
  int Setting { get; }
}

void Function<T>()
  where T : struct, ISample
{
  if (default(T).Setting == 13)
    Console.WriteLine("Тринадцать!");
  else
    Console.WriteLine(default(T).Setting);
}

readonly struct Sample7 : ISample
{
  public int Setting => 7;
}

readonly struct Sample13 : ISample
{
  public int Setting => 13;
}

Function<Sample7>(); // 7
Function<Sample13>();// Тринадцать!

Компилятор C# обрабатывает Function как обычную обобщённую функцию. JIT создаст две отдельные копии Function, поскольку Sample7 и Sample13 являются типами-значений. В обеих копиях код default(T).Setting передаётся как ограниченный виртуальный вызов. Тогда каждая копия метода имеет высокую вероятность оптимизации. Компилятор знает тип T для каждой копии. Когда он оптимизирует Function<Sample7>, он знает, что default(T).Setting вызывает метод ISample.get_Setting для типа Sample7. Реализация ISample.Setting в Sample7 тривиальна и, скорее всего, будет встроена, что означает, что ветвь if может быть предварительно вычислена. Очень вероятно, что обе копии Function<T> в итоге будут иметь только один вызов Console.WriteLine без оператора if. Статические абстрактные методы интерфейса позволяют нам ещё немного почистить код. Вместо того, чтобы определять Setting как метод экземпляра, он может быть статическим абстрактным методом:
interface ISample
{
  static abstract int Setting { get; }
}

void Function<T>()
 where T : struct, ISample
{
  if (T.Setting == 13)
    Console.WriteLine("Тринадцать!");
  else
    Console.WriteLine(T.Setting);
}

Теперь свойства Sample7 и Sample13 будут статическими, зато нет необходимости в default(T). Окончание следует… Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-3-generic-code-generation.html