C#Generics是如何实现的?

时间:2012-07-11 16:06:55

标签: c# generics polymorphism override

我曾经认为C#中的Generics已经实现了,当使用新的泛型类型时,在运行时或编译时生成了一个新的类/方法/你有什么,类似于C ++模板(我从未真正调查过,我很可能会错,我很乐意接受更正)。

但在我的编码中,我提出了一个确切的反例:

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();
        }
    }
}

这两个控制台行都打印出来。

我知道发生这种情况的实际原因是对Object.GetHashCode()的虚拟调用没有解析为Test.GetHashCode(),因为Test中的方法被标记为new而不是override。因此,我知道如果我在Test.GetHashCode()上使用“覆盖”而不是“新”,那么0的返回将多态地覆盖对象中的方法GetHashCode,这不是真的,而是根据我(之前)的理解对于C#泛型来说它不重要,因为T的每个实例都会被Test替换,因此方法调用将静态地(或在通用解析时)被解析为“new”方法。

所以我的问题是:如何在C#中实现泛型?我不知道CIL字节码,但我知道Java字节码,所以我理解面向对象的CLI语言如何工作在低位水平。随意在那个级别解释。

顺便说一句,我认为C#泛型是以这种方式实现的,因为与Java的类型擦除系统相比,每个人总是在C#“True Generics”中调用泛型系统。

1 个答案:

答案 0 :(得分:7)

GenericTest<T>.Get(T)中,C#编译器已选择 ,应该(虚拟地)调用object.GetHashCode。没有办法解决这个&#34; new&#34;运行时的GetHashCode方法(在方法表中有自己的插槽,而不是覆盖object.GetHashCode的插槽)。

来自Eric Lippert的What's the difference, part one: Generics are not templates,解释了这个问题(所使用的设置略有不同,但课程很好地适用于您的场景):

  

这表明C#中的泛型与C ++中的模板不同。   您可以将模板视为花式搜索和替换   机制。[...]这不是通用类型的工作方式;泛型类型是,   好吧,通用。我们执行重载分辨率一次并烘焙   结果。 [...]我们为通用类型生成的IL已经有了   它要调用的方法被挑出来了。抖动没说   “好吧,我碰巧知道如果我们要求C#编译器执行   现在有了这些额外的信息,那么它会选择一个   不同的超载。让我重写生成的代码来忽略   C#编译器最初生成的代码......“抖动知道   没有关于C#的规则。

为您所需的语义提供解决方法:

  

现在,如果您确实希望在运行时根据运行时类型重新执行重载决策   争论,我们可以为你做到;这就是新的“动态”   功能在C#4.0中完成。只需将“对象”替换为“动态”即可   你进行涉及该对象的调用,我们将运行重载   运行时的解析算法和动态吐出的调用代码   编译器会选择的方法,让它知道所有的   编译时的运行时类型。