第二个:
Object is Type
用string 和int 测试了这1'000'000'000 次:
//Release
00:00:18.1428040 //Object.GetType() == typeof(Type)
00:00:03.9791070 //Object is Type
//Debug
00:00:21.3545510 //Object.GetType() == typeof(Type)
00:00:06.2969510 //Object is Type
//Machine specs:
//Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
//6 GB RAM memory
//Ubuntu 14.04 (OS)
//Runtime: Mono JIT compiler version 3.2.8
//Compiler: Mono dmcs
//Notes: ran these with some background processes, but the difference in time
// is significant enough I guess.
注意:两者之间存在强烈的语义差异:
- 相等
== 检查类型相等:换句话说,如果A : B
对于A.GetType() == typeof(B),相等性测试将失败
而A is B 会成功。
- 如果对象是
null,它会抛出一个System.NullReferenceException。在第二种情况下,它将返回
false。
从编译器的角度来看,这是相当逻辑的:在第一个变体中,您查询对象的类型。如果没有真正优化,你先调用一个函数,然后调用说它必须返回一个指向类型表的指针。
在第二种情况下,您可以省略此类调用:编译器将通过返回类型代码来对其进行专门化。如果事先知道Type,它甚至可以对其进行非常快速的测试。
另外请注意,对于一些琐碎的情况,Object is Type 可以进行优化:例如,因为编译器已经可以推导出 Object 不能/始终是 Type 类型。
更高级
也可以分析CIL虚拟机源码,对于第一个变种,是这样的:
IL_0000: ldarg.0
IL_0001: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
IL_0006: ldtoken [mscorlib]System.Int32
IL_000b: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0010: call bool [mscorlib]System.Type::op_Equality(class [mscorlib]System.Type, class [mscorlib]System.Type)
IL_0015: ret
对于第二个变体,这是:
IL_0000: ldarg.0
IL_0001: isinst [mscorlib]System.Int32
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: ret
(当然也可以填写其他类型)。现在ldarg.0 和ret 只是使用方法的副产品,因此可以忽略它们。
我们看到的是,在第一个变体中,显式调用GetType 方法,然后调用== 运算符。函数调用通常很昂贵。在第二个变体中,它立即检查isinst。该代码需要更少的字节并使用更便宜的方法。尽管性能当然取决于运行时环境的实现,但我认为可以说第二个变体在性能上几乎总是优于第一个变体。
编译器可能特化第一个变体,使其运行与第二个变体一样高效,但 Mono C# 编译器似乎没有这样做。可能没有可用的 C# 编译器。