Mr Anderson
Mr Anderson

Reputation: 2233

Why is GetHashCode() method compiled differently for DateTime compared to other structs?

Consider the following methods in 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();
}

Let's look at the instructions generated by the compiler:

For the Decimal method:

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

For the Int64 method:

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

For the DateTime method:

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

Why is the DateTime.GetHashCode() method being treated as a virtual call of Object.GetHashCode(), considering there is an overridden GetHashCode() method for the DateTime struct?

Furthermore, I can create a method that directly calls the System.DateTime.GetHashCode() method without the virtual call using the following code:

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);

Then create a delegate to test it:

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.

Just curious why the method is implemented this way by default for Int64 and Decimal, but not DateTime.

Upvotes: 10

Views: 560

Answers (2)

svick
svick

Reputation: 244968

When it comes to Roslyn, what you're describing is an old behavior (Roslyn version 1.1.0 and older). The new behavior (version 1.2.0 and newer) is to use call for DateTime too.

The change was made in pull request String concat with char and similar primitives should call overriden ToString directly (#7080).

The problem with the constrained.callvirtcall optimization is that it means removing the override becomes a binary breaking change, so the optimization can't be applied universally. But it can be applied to types where the compiler can be sure that the override is not going to be removed.

The old behavior was to use this optimization for "intrinsic types" (those that have keywords in C#) and some special rarely used types. The new behavior is to use the optimization for all "special types", which include intrinsic types and also DateTime.

Upvotes: 7

hillin
hillin

Reputation: 1799

I tested your code on my machine, all three methods emit call instead of callvirt, so I guess this could be compiler-specific.

My guess is, earlier version of Csc emits call only for simple type virtual methods, so it was actually these simple types made special, not DateTime. Later, they decided that it's worth nothing to emit callvirt for value type method calls, as they would never be overridden. So all value type method calls are emitted with call, while reference type virtual method calls with callvirt.

PS. I have Visual Studio 2015 with .NET Framework 4.6.1 on my machine. I tested with .NET 2.0 to 4.6.1, all of them generates the same IL code (no callvirt for DateTime.GetHashCode).

Upvotes: 4

Related Questions