Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

.net

cil

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.

like image 680
Mr Anderson Avatar asked Apr 23 '16 05:04

Mr Anderson


People also ask

Is GetHashCode unique?

NO! A hash code is not an id, and it doesn't return a unique value. This is kind of obvious, when you think about it: GetHashCode returns an Int32 , which has “only” about 4.2 billion possible values, and there's potentially an infinity of different objects, so some of them are bound to have the same hash code.

How does GetHashCode work in C#?

GetHashCode method of the base class uses reflection to compute the hash code based on the values of the type's fields. In other words, value types whose fields have equal values have equal hash codes.

How do I find hash code?

The GetHashCode method provides this hash code for algorithms that need quick checks of object equality. Syntax: public virtual int GetHashCode (); Return Value: This method returns a 32-bit signed integer hash code for the current object.


2 Answers

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.

like image 177
svick Avatar answered Sep 21 '22 08:09

svick


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

like image 35
hillin Avatar answered Sep 19 '22 08:09

hillin