2014-02-11 1 views
4

Обзор (простите меня за то, что вы так подробно, но я предпочел бы, чтобы это было слишком много, чем слишком мало): я пытаюсь отредактируйте источник Dapper для нашего решения таким образом, чтобы при чтении любых данных DateTime или Nullable из базы данных его свойство DateTime.Kind всегда установлено на DateTimeKind.Utc.Unboxing Nullable, когда испускающий код для метода оставляет оценочный стек неожиданным (для меня) состоянием

В нашей системе все DateTimes, поступающие с лицевой стороны, гарантированы в UTC, а база данных (Sql Server Azure) хранит их как тип DateTime в UTC (мы не используем DateTimeOffsets, мы всегда всегда убедитесь, что DateTime является UTC, прежде чем хранить его в БД.)

Я читал все о том, как сгенерировать код для DynamicMethods, используя ILGenerator.Emit (...), и чувствую, что у меня есть достойное понимание как это работает с оценочным стеком, местными жителями и т. д. В моих усилиях по решению этой проблемы я написал небольшие примеры кода, которые помогут мне достичь конечной цели. Я написал DynamicMethod, чтобы взять DateTime в качестве аргумента, вызывать DateTime.SpecifyKind, вернуть значение. То же самое с DateTime? type, используя свойство Nullable.Value, чтобы получить DateTime для метода SpecifyKind.

В этом моя проблема возникает: в 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)); 
    } 

я получаю то, что я ожидал здесь. Ура! Но это еще не конец, потому что у нас есть unboxing, чтобы иметь дело с ...

[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, предположите, что DateTime на стек eval, а не DateTime?), но это также не удается.

Может кто-нибудь, пожалуйста, скажите мне, что мне не хватает?

+0

Just (и не обязательно связано с вашим вопросом): что на самом деле является общей целью использования динамически испускаемого кода в таком случае? Это просто производительность? Или еще? –

+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 
... 

Thee первая разница с версией является то, что ldarg используется вместо ldarga. Вы хотите, чтобы unbox.any проверял переданное значение, а не указатель на переданное значение. (ldarga работает тоже в моих тестах, но ldarg имеет смысл в любом случае.)

Второй, и более актуальной, разница с версией является то, что после того, как unbox.any, значение сохраняется, а затем ссылка на это место является загружен.Это связано с тем, что неявный параметр методов экземпляров типов значений имеет тип 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

Смежные вопросы