Reputation: 2233
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
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.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
.
Upvotes: 7
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