我曾经认为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”中调用泛型系统。
答案 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中完成。只需将“对象”替换为“动态”即可 你进行涉及该对象的调用,我们将运行重载 运行时的解析算法和动态吐出的调用代码 编译器会选择的方法,让它知道所有的 编译时的运行时类型。