概述(请原谅我这么详细,但我宁愿这太过分了):我正在尝试以这样的方式编辑Dapper源代码,以便在读取任何DateTime或Nullable时从数据库中,其DateTime.Kind属性始终设置为DateTimeKind.Utc。
在我们的系统中,来自前端的所有DateTime都保证是UTC时间,并且数据库(Sql Server Azure)将它们存储为UTC中的DateTime类型(我们不使用DateTimeOffsets,我们只是一直在制作确保DateTime为UTC,然后再将其存储在数据库中。)
我一直在阅读有关如何使用ILGenerator.Emit(...)为DynamicMethods生成代码的所有内容,并且感觉我对它如何与评估堆栈,本地人等一起工作有一个很好的理解。在我的努力中为了解决这个问题,我编写了一些代码示例来帮助我实现最终目标。我写了一个DynamicMethod来取一个DateTime作为参数,调用DateTime.SpecifyKind,返回值。然后与DateTime相同? type,使用其Nullable.Value属性获取SpecifyKind方法的DateTime。
这就是我的问题所在:在dapper中,DateTime(或DateTime?我实际上并不知道,但是当我把它视为我得不到我期望的那样时)是盒装的。因此,当我尝试使用OpCodes.Unbox或OpCodes.Unbox_Any时,将结果视为DateTime或DateTime?,我得到VerificationException:操作可能会破坏运行时的稳定性。
显然我错过了一些关于拳击的重要内容,但我会给你我的代码示例,也许你可以帮助我让它工作。
这有效:
[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));
}
我得到了我的期望。好极了!但它还没有结束,因为我们已经拆箱了......
[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));
}
这只是直接上升不会运行。我得到了VerificationException,然后我在角落里哭了一会儿,直到我准备再试一次。
我试过期待DateTime而不是DateTime? (在unbox之后,假设在eval堆栈上使用DateTime而不是DateTime?)但是也失败了。
有人可以告诉我我错过了什么吗?
答案 0 :(得分:7)
如果有疑问,请编写一个执行相同操作的最小C#库,并查看编译的内容:
您的尝试似乎等同于
using System;
static class Program {
public static DateTime? SetUtc(object value) {
return new DateTime?(DateTime.SpecifyKind(((DateTime?)value).Value, DateTimeKind.Utc));
}
};
并编译为:
$ mcs test.cs -target:library -optimize+ && monodis test.dll ... IL_0000: ldarg.0 IL_0001: unbox.any valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime> IL_0006: stloc.0 IL_0007: ldloca.s 0 IL_0009: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::get_Value() IL_000e: ldc.i4.1 IL_000f: call valuetype [mscorlib]System.DateTime valuetype [mscorlib]System.DateTime::SpecifyKind(valuetype [mscorlib]System.DateTime, valuetype [mscorlib]System.DateTimeKind) IL_0014: newobj instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::'.ctor'(!0) IL_0019: ret ...
与您的版本的第一个区别是ldarg
代替ldarga
。您希望unbox.any
检查传递的值,而不是指向传递的值的指针。 (ldarga
在我的测试中也有效,但无论如何ldarg
更有意义。)
与您的版本的第二个,也是更相关的差异是,在unbox.any
之后,存储该值,然后加载对该位置的引用。这是因为值类型的实例方法的隐式this
参数具有类型ref T
,而不是您习惯用于引用类型的方法的T
。如果我确实包含stloc.0
/ ldloca.s 0
,那么您的代码会在我的系统上通过测试。
但是,当您在转换为Value
后无条件阅读DateTime?
属性时,您也可以直接转到DateTime
并完全避免此问题。唯一的区别是当传入错误类型的值时会得到哪个异常。
如果您想要类似
的内容public static DateTime? SetUtc(object value) {
var local = value as DateTime?;
return local == null ? default(DateTime?) : DateTime.SpecifyKind(local.Value, DateTimeKind.Utc);
}
然后我会使用像
这样的东西var label1 = il.DefineLabel();
var label2 = il.DefineLabel();
il.Emit(OpCodes.Ldarg_S, 0); // object
il.Emit(OpCodes.Isinst, typeof(DateTime)); // boxed DateTime
il.Emit(OpCodes.Dup); // boxed DateTime, boxed DateTime
il.Emit(OpCodes.Brfalse_S, label1); // boxed DateTime
il.Emit(OpCodes.Unbox_Any, typeof(DateTime)); // unboxed DateTime
il.Emit(OpCodes.Ldc_I4_1); // unboxed DateTime, int
il.Emit(OpCodes.Call, typeof(DateTime).GetMethod("SpecifyKind")); // unboxed DateTime
il.Emit(OpCodes.Newobj, typeof(DateTime?).GetConstructor(new[] { typeof(DateTime) })); // unboxed DateTime?
il.Emit(OpCodes.Br_S, label2);
il.MarkLabel(label1); // boxed DateTime (known to be null)
il.Emit(OpCodes.Unbox_Any, typeof(DateTime?)); // unboxed DateTime?
il.MarkLabel(label2); // unboxed DateTime?
il.Emit(OpCodes.Ret);