¿Cómo se implementan C # Generics?

Pensé que los genéricos en C # se implementaron de tal manera que se generó una nueva clase / método / qué-has-you, ya sea en tiempo de ejecución o en tiempo de compilación, cuando se usó un nuevo tipo genérico, similar a las plantillas de C ++ (que En realidad nunca he investigado y muy bien podría estar equivocado, sobre lo cual aceptaría con gusto la corrección).

Pero en mi codificación se me ocurrió un contraejemplo exacto:

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's hashcode from GenericTest!");
        }
        if (reg == testVar.GetHashCode())
        {
            Console.WriteLine("Got Test's 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();
        }
    }
}

Ambas líneas de consola se imprimen.

Sé que la razón real por la que esto sucede es que la llamada virtual a Object.GetHashCode () no se resuelve a Test.GetHashCode () porque el método en Test está marcado como nuevo en lugar de anularlo. Por lo tanto, sé que si usé "anulación" en lugar de "nuevo" en Test.GetHashCode (), entonces el retorno de 0 anularía polimorfamente el método GetHashCode en el objeto y esto no sería cierto, pero de acuerdo con mi comprensión (anterior) de los genéricos de C # no habría importado porque todas las instancias de T se hubieran reemplazado con Test y, por lo tanto, la llamada al método se habría resuelto de forma estática (o en tiempo de resolución genérica) al método "nuevo".

Así que mi pregunta es esta:¿Cómo se implementan los genéricos en C #? No sé el código de bytes de CIL, pero sí conozco el código de Java, por lo que entiendo cómo funcionan los lenguajes CLI orientados a objetos en un nivel bajo. Siéntase libre de explicar a ese nivel.

Dejando de lado, pensé que los genéricos de C # se implementaron de esa manera porque todos siempre llaman al sistema genérico en "Genéricos verdaderos" de C #, en comparación con el sistema de borrado de tipos de Java.

Respuestas a la pregunta(1)

Su respuesta a la pregunta