2014-02-11 2 views
4

개요 (너무 자세하게 설명해 주셔서 용서해주세요.하지만 너무 조금 너무 많을 것입니다.) : 시도 중입니다. 데이터베이스에서 DateTime 또는 Nullable을 읽을 때 해당 DateTime.Kind 속성이 항상 DateTimeKind.Utc로 설정되는 방식으로 Dapper 소스를 편집하십시오.메소드에 대한 코드를 내보내는 중에 예상치 못한 (나에게) 상태의 평가 스택이 남을 때 Unboxing Nullable

프런트 엔드에서 오는 모든 DateTimes는 UTC 시간으로 보장되며 데이터베이스 (SQL Server Azure)는 UTC로 DateTime 형식으로 저장합니다 (우리는 DateTimeOffsets를 사용하지 않습니다. 우리는 항상

나는 ILGenerator.Emit (...)을 사용하여 DynamicMethods에 대한 코드를 생성하는 방법에 대해 모두 읽었으며 필자는 적절한 이해를하고 있다고 느낍니다. 평가 스택, 지역 주민 등에서 작동하는 방식이 문제를 해결하기위한 노력의 일환으로 최종 목표를 달성 할 수 있도록 코드 샘플을 작성했습니다. DateTime을 인수로 사용하고 DateTime.SpecifyKind를 호출하여 값을 반환하는 DynamicMethod를 작성했습니다. 그렇다면 DateTime과 동일합니까? Nullable.Value 속성을 사용하여 SpecifyKind 메서드의 DateTime을 가져옵니다.

이것은 내 문제가있는 곳입니다 : 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 후, DateTime 대신에 DateTime을 사용하는 것으로 가정) DateTime은 실패하지만 역시 실패합니다.

내가 누락 된 부분을 알려주실 수 있습니까?

+0

같은 것을 사용하는 것이 원하는 경우 호기심 (그리고 반드시 귀하의 질문에 관련되지 않음) : 실제로 그런 경우 동적으로 방출 된 코드를 사용하는 일반적인 목적은 무엇입니까? 그것은 단지 성과입니까? 아니면 sth? –

+0

@KubaWyrostek (위의 스 니펫이 아니라 위의 스 니펫이 아닌) 수정하려는 코드는 Dapper ORM (매우 빠르고 가벼운 ORM이며 Stackoverflow가 실제로이 코드를 사용합니다). 리플렉션을 사용하여 SQL deserializer 메소드를 작성하여 SQL 출력 오브젝트를 거의 0 구성으로 지정된 도메인 오브젝트에 맵핑합니다! 이러한 디시리얼라이저는 도메인 객체 중 다른 하나를 deserialize해야 할 때마다 캐시되도록 캐시됩니다. 그래서이 경우 최소 구성에 대한 내 요구 사항을 충족시키고 성능이 뛰어납니다! – 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 
... 

귀하의 버전과의 첫 번째 다른 점은 ldarga 대신 ldarg이 사용된다는 것입니다. unbox.any에 전달 된 값을 확인해야하며 전달 된 값의 포인터는 필요하지 않습니다. (ldarga 내 테스트에서 너무 작동 않지만, ldarg 어쨌든 더 의미가 있습니다.)

두 번째, 그리고 더 많은 관련, 버전과의 차이는 unbox.any 후 값이 저장되어 있으며, 그 위치에 대한 참조입니다 짐을 실은.이는 값 유형의 인스턴스 메소드에 대한 암시적인 this 매개 변수가 참조 유형의 메소드에 익숙한 T이 아닌 ref T 유형을 가지기 때문입니다. stloc.0/ldloca.s 0을 포함하면 코드가 내 시스템에 테스트를 통과합니다.

그러나 DateTime?으로 전송 한 후 무조건 Value 속성을 읽으면 마찬가지로 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