5

概述(请原谅我如此详细,但我宁愿它太多也不愿太少):我正在尝试编辑我们解决方案的 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也一样吗?类型,使用其 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?(拆箱后,假设 DateTime 在 eval 堆栈上,而不是 DateTime?)但这也失败了。

有人可以告诉我我错过了什么吗?

4

1 回答 1

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: 调用实例 !0 valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::get_Value()
        IL_000e:ldc.i4.1
        IL_000f:调用 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:恢复
...

与您的版本的第一个区别ldarg是使用而不是ldarga. 您要unbox.any检查传递的值,而不是指向传递值的指针。(ldarga在我的测试中也有效,但ldarg无论如何更有意义。)

与您的版本的第二个更相关的区别是,在 之后unbox.any,存储该值,然后加载对该位置的引用。这是因为this值类型的实例方法的隐式参数具有 type 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);
于 2014-02-11T20:09:14.703 回答