C#Generics是如何实现的?
我曾经认为C#中的Generics是这样实现的,当使用新的generics类型时,在运行时或编译时生成一个新的类/方法/你有什么,类似于C ++模板(我从来没有真正调查,我很可能是错的,我很乐意接受纠正。
但在我的编码中,我提出了一个确切的反例:
static class Program { static void Main() { Test testVar = new Test(); GenericTest genericTest = new GenericTest(); 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 { 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#generics来说它不重要,因为T的每个实例都将被Test替换,因此方法调用将静态地(或在通用解析时)被解析为“new”方法。
所以我的问题是: 如何在C#中实现generics? 我不知道CIL字节码,但我知道Java字节码,所以我理解面向对象的CLI语言如何在低级别工作。 随意在那个级别解释。
顺便说一句,我认为C#generics是以这种方式实现的,因为与Java的类型擦除系统相比,每个人总是在C#“True Generics”中调用generics系统。
在GenericTest
,C#编译器已经选择了该object.GetHashCode
应该(虚拟地)调用object.GetHashCode
。 这将无法解决运行时的“新” GetHashCode
方法(它将在方法表中拥有自己的插槽,而不是覆盖object.GetHashCode
的插槽)。
来自Eric Lippert的有什么区别,第一部分:generics不是模板 ,问题被解释(使用的设置略有不同,但课程很好地适用于您的场景):
这说明C#中的generics与C ++中的模板不同。 您可以将模板视为一种花哨的搜索和替换机制。[…]这不是generics类型的工作方式; generics类型是通用的 。 我们执行一次重载决策并烘焙结果。 […]我们为generics类型生成的IL已经有了它要调用的方法。 抖动并没有说“好吧,我碰巧知道如果我们要求C#编译器立即执行此附加信息,那么它会选择不同的重载。 让我重写生成的代码,忽略C#编译器最初生成的代码……“抖动对C#的规则一无所知。
并为您所需的语义解决方法:
现在,如果您确实希望在运行时根据参数的运行时类型重新执行重载决策,我们可以为您执行此操作; 这就是新的“动态”function在C#4.0中的作用。 只需用“动态”替换“对象”,当你进行涉及该对象的调用时,我们将在运行时运行重载决策算法并动态吐出调用编译器选择的方法的代码,让它知道所有运行时编译时的类型。