メソッドのコードを出力すると、予期せぬ(私にとって)状態に評価スタックが残るときに、Nullableをアンボックスする

c# dapper dynamicmethod reflection.emit unboxing

質問

概要(私があまりにも細かすぎるとは思えないが、あまりにも少なすぎると思う):私たちのソリューションのDapperソースを編集して、DateTimeまたはNullableがデータベースから読み込まれるようにしようとしているそのDateTime.Kindプロパティは常にDateTimeKind.Utcに設定されます。

私たちのシステムでは、フロントエンドから来るすべてのDateTimesはUTC時間であることが保証されており、データベース(SQL Server Azure)はDateTime型としてUTCで格納しています(DateTimeOffsetsは使用していません。 DBに格納する前はUTCです)。

私は、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ではなく、evalスタック上の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いくが、 ldargはとにかく意味をなさない)

第2の、より関連性のあるバージョンの違いは、 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);


ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ