Overview (forgive me for being so detailed, but I'd rather it be too much than too little): I'm attempting to edit the Dapper source for our solution in such a way that when any DateTime or Nullable is read from the database, its DateTime.Kind property is always set to DateTimeKind.Utc.
In our system, all DateTimes coming from the front end are guaranteed to be in UTC time, and the database (Sql Server Azure) is storing them as DateTime type in UTC (We are not using DateTimeOffsets, we are just always making sure the DateTime is UTC before storing it in the DB.)
I have been reading all about how to generate code for DynamicMethods by using ILGenerator.Emit(...), and feel like I have a decent understanding of how it works with the evaluation stack, locals, etc. In my efforts to solve this issue, I have written small samples of code to help get me to the end goal. I wrote a DynamicMethod to take a DateTime as an argument, call DateTime.SpecifyKind, return the value. Then the same with DateTime? type, using its Nullable.Value property to get the DateTime for the SpecifyKind method.
This is where my problem comes in: In dapper, the DateTime (or DateTime? I don't actually know, but when I treat it as though it is either I am not getting what I expect) is boxed. So when I try to use OpCodes.Unbox or OpCodes.Unbox_Any, then treat the result as either DateTime or DateTime?, I get a VerificationException: Operation could destabilize the runtime.
Obviously I'm missing something important about boxing, but I'll give you my code samples and maybe you can help me get it working.
This works:
[Test]
public void Reflection_Emit_Test3()
{
//Setup
var dm = new DynamicMethod("SetUtc", typeof(DateTime?), new Type[] {typeof(DateTime?)});
var nullableType = typeof(DateTime?);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarga_S, 0); // [DateTime?]
il.Emit(OpCodes.Call, nullableType.GetProperty("Value").GetGetMethod()); // [DateTime]
il.Emit(OpCodes.Ldc_I4, (int)DateTimeKind.Utc); // [DateTime][Utc]
il.Emit(OpCodes.Call, typeof(DateTime).GetMethod("SpecifyKind")); //[DateTime]
il.Emit(OpCodes.Newobj, nullableType.GetConstructor(new[] {typeof (DateTime)})); //[DateTime?]
il.Emit(OpCodes.Ret);
var meth = (Func<DateTime?, DateTime?>)dm.CreateDelegate(typeof(Func<DateTime?, DateTime?>));
DateTime? now = DateTime.Now;
Assert.That(now.Value.Kind, Is.Not.EqualTo(DateTimeKind.Utc));
//Act
var nowUtc = meth(now);
//Verify
Assert.That(nowUtc.Value.Kind, Is.EqualTo(DateTimeKind.Utc));
}
I get what I expect here. Yay! But it's not over yet, because we have unboxing to deal with...
[Test]
public void Reflection_Emit_Test4()
{
//Setup
var dm = new DynamicMethod("SetUtc", typeof(DateTime?), new Type[] { typeof(object) });
var nullableType = typeof(DateTime?);
var il = dm.GetILGenerator();
il.DeclareLocal(typeof (DateTime?));
il.Emit(OpCodes.Ldarga_S, 0); // [object]
il.Emit(OpCodes.Unbox_Any, typeof(DateTime?)); // [DateTime?]
il.Emit(OpCodes.Call, nullableType.GetProperty("Value").GetGetMethod()); // [DateTime]
il.Emit(OpCodes.Ldc_I4, (int)DateTimeKind.Utc); // [DateTime][Utc]
il.Emit(OpCodes.Call, typeof(DateTime).GetMethod("SpecifyKind")); //[DateTime]
il.Emit(OpCodes.Newobj, nullableType.GetConstructor(new[] { typeof(DateTime) })); //[DateTime?]
il.Emit(OpCodes.Ret);
var meth = (Func<object, DateTime?>)dm.CreateDelegate(typeof(Func<object, DateTime?>));
object now = new DateTime?(DateTime.Now);
Assert.That(((DateTime?) now).Value.Kind, Is.Not.EqualTo(DateTimeKind.Utc));
//Act
var nowUtc = meth(now);
//Verify
Assert.That(nowUtc.Value.Kind, Is.EqualTo(DateTimeKind.Utc));
}
This just straight up won't run. I get the VerificationException, and then I cry in the corner for a while until I'm ready to try again.
I have tried expecting a DateTime instead of a DateTime? (after unbox, assume DateTime on eval stack, rather than DateTime?) but that fails as well.
Can someone please tell me what I'm missing?