为什么与其他结构相比,DateTime的GetHashCode()方法编译方式不同?

时间:2016-04-23 05:31:58

标签: c# .net cil

在C#中考虑以下方法:

public static int HashCodeFunction(Decimal value)
{
    return value.GetHashCode();
}
public static int HashCodeFunction(Int64 value)
{
    return value.GetHashCode();
}
public static int HashCodeFunction(DateTime value)
{
    return value.GetHashCode();
}

让我们看一下编译器生成的指令:

对于Decimal方法:

ldarga.s Parameter:System.Decimal value
call Method:System.Decimal.GetHashCode()
ret

对于Int64方法:

ldarga.s Parameter:System.Int64 value
call Method:System.Int64.GetHashCode()
ret

对于DateTime方法:

ldarga.s Parameter:System.DateTime value
constrained Type:System.DateTime
callvirt Method:System.Object.GetHashCode()
ret

为什么DateTime.GetHashCode()方法被视为Object.GetHashCode()的虚拟调用,考虑到GetHashCode()结构有一个被覆盖的DateTime方法?

此外,我可以使用以下代码创建一个直接调用System.DateTime.GetHashCode()方法而无需虚拟调用的方法:

DynamicMethod myDynamicMethod = new DynamicMethod("myHashCodeMethod", typeof(int), new[] { typeof(DateTime) });

ILGenerator gen = myDynamicMethod.GetILGenerator();
LocalBuilder local = gen.DeclareLocal(typeof(DateTime));
gen.Emit(OpCodes.Ldarga_S, local);
gen.Emit(OpCodes.Call, typeof(DateTime).GetMethod("GetHashCode"));
gen.Emit(OpCodes.Ret);

然后创建一个委托来测试它:

Func<DateTime, int> myNewHashCodeFunction = (Func<DateTime,int>)myDynamicMethod.CreateDelegate(typeof(Func<DateTime, int>));

DateTime dt = DateTime.Now;

int myHashCode = myNewHashCodeFunction(dt);
int theirHashCode = dt.GetHashCode();
// These values are the same.

很好奇为什么默认情况下Int64Decimal采用这种方法实现该方法,而不是DateTime

2 个答案:

答案 0 :(得分:7)

说到Roslyn,你所描述的是旧行为(Roslyn版本1.1.0及更早版本)。新行为(版本1.2.0及更高版本)也将call用于DateTime

更改是在pull request String concat with char and similar primitives should call overriden ToString directly (#7080)

constrained.callvirtcall优化的问题在于,它意味着删除覆盖变为二进制分解更改,因此优化无法普遍应用。但它可以应用于编译器可以确保不会删除覆盖的类型。

旧的行为是将这种优化用于&#34;内在类型&#34; (在C#中有关键字的那些)和一些特殊的很少使用的类型。新的行为是对所有&#34;特殊类型&#34;使用优化,其中包括内在类型和DateTime

答案 1 :(得分:4)

我在我的机器上测试了你的代码,所有三种方法都发出call而不是callvirt,所以我想这可能是编译器特有的。

我的猜测是,早期版本的Csc仅为simple type个虚拟方法发出call,所以实际上这些简单类型是特殊的,而不是DateTime。后来,他们认为为值类型方法调用发出callvirt是没有价值的,因为它们永远不会被覆盖。因此,所有值类型方法调用都使用call发出,而引用类型虚拟方法调用使用callvirt

PS。我在我的机器上安装了带有.NET Framework 4.6.1的Visual Studio 2015。我使用.NET 2.0到4.6.1进行了测试,所有这些都生成了相同的IL代码(callvirt没有DateTime.GetHashCode)。