概述(請原諒我是如此詳細,但我寧願它太多,而不是太少):我試圖編輯我們解決方案的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?),但也失敗了。
有人可以告訴我我錯過了什麼嗎?
剛好奇心(而不一定與你的問題有關):在這種情況下使用動態發射代碼的實際目的是什麼?這只是表現嗎?還是別的? –
@KubaWyrostek我試圖修改的代碼(不是上面的代碼片段,儘管它們是相關的)在Dapper ORM裏面(這是一個非常快速,輕量級的ORM;我相信Stackoverflow實際上在幕後使用它)。它使用反射來創建動態反序列化器方法,將SQL輸出對象映射到指定的域對象,其配置幾乎爲零!這些反序列化程序隨後被緩存,以便每當另一個域對象需要反序列化時使用這些反序列化程序。所以在這種情況下,它滿足了我最小的配置需求,並且性能卓越! – Anj