當發出方法的代碼時,取消裝箱Nullable會使評估堆棧處於意外(對我)狀態

c# dapper dynamicmethod reflection.emit unboxing

概述(原諒我如此詳細,但我寧願它太過分了):我正在嘗試編輯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?)但是也失敗了。

有人可以告訴我我錯過了什麼嗎?

一般承認的答案

如有疑問,請編寫一個執行相同操作的最小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 ,那麼你的代碼會在我的系統上傳遞它的測試。

但是,在轉換為DateTime?之後無條件地讀取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);


許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因