Как реализованы С# Generics?

Я думал, что Generics в С# были реализованы так, что новый класс/метод/what-have-you был сгенерирован либо во время выполнения, либо во время компиляции, когда использовался новый общий тип, аналогичный шаблонам С++ (который я никогда не рассматривал, и я очень хорошо мог ошибаться, о котором я с радостью принимаю исправление).

Но в моем кодировании я придумал точный контрпример:

static class Program {
    static void Main()
    {
        Test testVar = new Test();

        GenericTest<Test> genericTest = new GenericTest<Test>();
        int gen = genericTest.Get(testVar);

        RegularTest regTest = new RegularTest();
        int reg = regTest.Get(testVar);

        if (gen == ((object)testVar).GetHashCode())
        {
            Console.WriteLine("Got Object hashcode from GenericTest!");
        }
        if (reg == testVar.GetHashCode())
        {
            Console.WriteLine("Got Test hashcode from RegularTest!");
        }
    }

    class Test
    {
        public new int GetHashCode()
        {
            return 0;
        }
    }

    class GenericTest<T>
    {
        public int Get(T obj)
        {
            return obj.GetHashCode();
        }
    }

    class RegularTest
    {
        public int Get(Test obj)
        {
            return obj.GetHashCode();
        }
    }
}

Печать обеих этих строк консоли.

Я знаю, что фактическая причина этого заключается в том, что виртуальный вызов Object.GetHashCode() не разрешен Test.GetHashCode(), потому что метод в тесте помечен как новый, а не переопределяющий. Поэтому я знаю, что если бы я использовал "переопределить", а не "новый" в Test.GetHashCode(), то возврат 0 мог бы полиморфно переопределить метод GetHashCode в объекте, и это было бы неверно, но в соответствии с моим (предыдущим) пониманием из генераторов С# это не имело бы значения, потому что каждый экземпляр T был бы заменен Test, и, таким образом, вызов метода был бы статически (или в течение общего времени разрешения) разрешен к "новому" методу.

Итак, мой вопрос таков: Как генерируются обобщения на С#? Я не знаю байт-код CIL, но я знаю байт-код Java, поэтому я понимаю, как объектно-ориентированные языки CLI работают на низком уровне уровень. Не стесняйтесь объяснять на этом уровне.

В стороне, я думал, что С# generics были реализованы таким образом, потому что каждый всегда вызывает общую систему в С# "True Generics" по сравнению с системой стирания типов Java.

Ответ 1

В GenericTest<T>.Get(T) компилятор С# уже выбрал, что object.GetHashCode должен быть вызван (фактически). Нет способа решить этот "новый" GetHashCode метод во время выполнения (который будет иметь свой собственный слот в таблице методов, вместо того, чтобы переопределять слот для object.GetHashCode).

От Эрика Липперта Какая разница, часть первая: Generics не являются шаблонами, проблема объясняется (используемая настройка немного отличается, но уроки хорошо переносятся на ваш сценарий):

Это иллюстрирует, что дженерики в С# не похожи на шаблоны в С++. Вы можете придумать шаблоны как поиск и замену причудливых штанов механизм. [...] Это не то, как работают общие типы; общие типы, ну, общий. Мы выполняем разрешение перегрузки один раз и запекаем в результат. [...] ИЛ, порожденный для родового типа, уже имеет метод, который он собирается назвать выбранным. Джиттер не говорит "хорошо, я знаю, что если мы попросим компилятора С# выполнить прямо сейчас с этой дополнительной информацией, тогда она бы выбрала различная перегрузка. Позвольте мне переписать сгенерированный код, чтобы игнорировать код, сгенерированный компилятором С#..." Джиттер знает ничего о правилах С#.

И обходной путь для вашей желаемой семантики:

Теперь, если вы хотите, чтобы разрешение перегрузки было повторно выполнено во время выполнения на основе типов времени выполнения аргументы, мы можем сделать это для вас; вот что нового "динамичного" функция работает в С# 4.0. Просто замените "объект" на "динамический" и когда вы делаете вызов с участием этого объекта, хорошо запускаете перегрузку алгоритм разрешения во время выполнения и динамический код, который вызывает метод, который был бы выбран компилятором, если бы он знал все во время компиляции.