今天又在看C#泛型的代码,又有一点小发现...但愿大家没听腻了我说泛型
是这样的,还是我上次写的那两个类:
interface ITest
{
void testMethod();
}
class Test<T> where T:ITest,new()
{
public Test()
{
t = new T();
}
public void foo()
{
t.testMethod();
}
private T t;
}
观察Test<T>.foo()函数的IL代码的时候,发现了一个很可疑的box操作:
.method public hidebysig instance void foo() cil managed
{
// Code size 17 (0x11)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld !0 class ConsoleApplication1.Test<!0>::t
IL_0006: box !0
IL_000b: callvirt instance void ConsoleApplication1.ITest::testMethod()
IL_0010: ret
} // end of method Test::foo
说明一下,那个ConsoleApplication是namespace,!0 是IL中泛型参数T的占位符。
从上面的代码来看,泛型参数的实例 Test<T>::t,被当做一个ValueType,而且在调用相关方法的时候被box成了一个Constraint里定义的接口,然后使用了callvirt来调用接口的虚函数。如此一来,又有box,又有转型的动作,似乎调用方法的开销大了一点
写了一个实现同样功能的多态版本,进行对比:
class TestVirtual
{
public static void foo(ITest i)
{
i.testMethod();
}
}
相应的foo函数产生的IL代码如下:
.method public hidebysig static void foo(class ConsoleApplication1.ITest i) cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: callvirt instance void ConsoleApplication1.ITest::testMethod()
IL_0006: ret
} // end of method TestVirtual::foo
明显代价要小很多。
并且如果把上述泛型的IL代码改为:
.method public hidebysig instance void foo() cil managed
{
// Code size 17 (0x11)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld !0 class ConsoleApplication1.Test<!0>::t
IL_0006: box !0
IL_000b: call instance void !0::testMethod()
IL_0010: ret
} // end of method Test::foo
结果会产生一个System.MissingMethodException,提示信息如下:
Unhandled Exception: System.MissingMethodException: Method not found: 'Void System.Object.testMethod()'.
从这里可以看出泛型参数的实例被box成了Object的实例,这到与隐含的泛型参数继承自Object 的Constraints吻合。看样子CLR的泛型支持充其量也就是一些底层的转型动作,而且还加上一个box的开销也不知道和java的泛型比,谁的开销更大一点了。
最后说一些题外话,看到Ninputer在抱怨不能约束ValueType或者ReferenceType,想到上次在C# team的blog里看到过这个事情,确实有准备添加这个功能啊,这里!