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
.
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.
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.
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.
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.callvirt
→ call
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
.
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With