2014-02-11 39 views
4

概述(請原諒我是如此詳細,但我寧願它太多,而不是太少):我試圖編輯我們解決方案的Dapper源代碼,以便在從數據庫讀取任何DateTime或Nullable時,其DateTime.Kind屬性始終設置爲DateTimeKind.Utc。取消裝箱可爲空時發射代碼的方法離開評估堆棧在一個意想不到的(對我)狀態

在我們的系統中,所有來自前端的DateTime都保證爲UTC時間,並且數據庫(Sql Server Azure)將它們存儲爲UTC中的DateTime類型(我們不使用DateTimeOffsets,我們只是總是確保DateTime在將其存儲在數據庫中之前爲Date)。

我一直在閱讀有關如何使用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? (在unbox之後,假設eval堆棧上的DateTime,而不是DateTime?),但也失敗了。

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

+0

剛好奇心(而不一定與你的問題有關):在這種情況下使用動態發射代碼的實際目的是什麼?這只是表現嗎?還是別的? –

+0

@KubaWyrostek我試圖修改的代碼(不是上面的代碼片段,儘管它們是相關的)在Dapper ORM裏面(這是一個非常快速,輕量級的ORM;我相信Stackoverflow實際上在幕後使用它)。它使用反射來創建動態反序列化器方法,將SQL輸出對象映射到指定的域對象,其配置幾乎爲零!這些反序列化程序隨後被緩存,以便每當另一個域對象需要反序列化時使用這些反序列化程序。所以在這種情況下,它滿足了我最小的配置需求,並且性能卓越! – Anj

回答

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); 
+0

謝謝您的及時和詳細的回覆!我剛下班回家,所以當我明天到辦公室時,我會給這個鏡頭一個鏡頭。如果它幫助我更接近我的解決方案,我會將其標記爲答案。 :) – Anj