7

System.Ling.ExpressionsAPIを使用して式を作成およびコンパイルしています。コンパイルは正常に機能しますが、コンパイルされたラムダを実行すると、原因不明のNullReferenceExceptionsまたはSystem.Security.Verification例外が発生する場合があります。参考までに、このプロジェクトの目的は、.NETタイプのカスタムシリアライザー関数を作成およびコンパイルすることです。

以下は、NullReferenceExceptionをスローする式のDebugInfoです。

.Lambda #Lambda1<System.Action`2[IO.IWriter,<>f__AnonymousType1`2[System.Int32[],System.Int32]]>(
    IO.IWriter $writer,
    <>f__AnonymousType1`2[System.Int32[],System.Int32] $t) {
    .Block() {
        .Invoke (.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>)(
            $writer,
            $t.a);
        .Invoke (.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>)(
            $writer,
            $t.b)
    }
}

.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>(
    IO.IWriter $writer,
    System.Int32[] $t) {
    .Block() {
        .Invoke (.Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>)(
            $writer,
            .Call System.Linq.Enumerable.Count((System.Collections.Generic.IEnumerable`1[System.Int32])$t));
        .Call IO.SerializerHelpers.WriteCollectionElements(
            (System.Collections.Generic.IEnumerable`1[System.Int32])$t,
            $writer,
            .Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>)
    }
}

.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $writer,
    System.Int32 $t) {
    .Call $writer.WriteInt($t)
}

.Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $w,
    System.Int32 $count) {
    .Call $w.BeginWritingCollection($count)
}

例外は、WriteCollectionElementsから繰り返し呼び出される#Lambda3の呼び出し内でスローされます。WriteCollectionElementsの実装は次のとおりです。

static void WriteCollectionElements<T>(IEnumerable<T> collection, IWriter writer, Action<IWriter, T> writeAction)
        {
            foreach (var element in collection)
            {
                writeAction(writer, element);
            }
        }

この関数内のデバッグから、例外がスローされたときに、collection、writer、writeAction、およびelementがすべてnull以外であると判断しました。コンパイルされたラムダに渡す引数は次のとおりです。

new { a = new[] { 20, 10 }, b = 2 }

また、bプロパティを削除してシリアライザー関数を再生成すると、すべてが正常に機能することも奇妙です。この場合、シリアライザーのDebugInfoは次のとおりです。

.Lambda #Lambda1<System.Action`2[IO.IWriter,<>f__AnonymousType5`1[System.Int32[]]]>(
    IO.IWriter $writer,
    <>f__AnonymousType5`1[System.Int32[]] $t) {
    .Block() {
        .Invoke (.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>)(
            $writer,
            $t.a)
    }
}

.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>(
    IO.IWriter $writer,
    System.Int32[] $t) {
    .Block() {
        .Invoke (.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>)(
            $writer,
            .Call System.Linq.Enumerable.Count((System.Collections.Generic.IEnumerable`1[System.Int32])$t));
        .Call IO.SerializerHelpers.WriteCollectionElements(
            (System.Collections.Generic.IEnumerable`1[System.Int32])$t,
            $writer,
            .Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>)
    }
}

.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $w,
    System.Int32 $count) {
    .Call $w.BeginWritingCollection($count)
}

.Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $writer,
    System.Int32 $t) {
    .Call $writer.WriteInt($t)
}

.NET Framework 4(少なくともそれが私のビルドターゲットです)をWindows 7、VS Express C#2010で実行しています。

誰かが何がうまくいかないのか、デバッグを試みるための次のステップを知っていますか?お役に立てれば、さらに詳しい情報を投稿させていただきます。

編集:私はそれ以来(私の知る限り)このバグを回避する方法を見つけましたが、なぜそれが起こるのか理解するのに近づいていません。上記で投稿した式を生成するコードには、次のものがあります。

MethodInfo writeCollectionElementsMethod = // the methodInfo for WriteCollectionElements with .MakeGenericMethod() called with typeof(T)
Expression<Action<IWriter, T> writeActionExpression = // I created this expression separately
ParameterExpression writerParameter, enumerableTParameter = // parameters of type IWriter and IEnumerable<T>, respectively

// make an expression to invoke the method
var methodCallExpression = Expression.Call(
    instance: null, // static
    method: writeCollectionElementsMethod,
    arguments: new[] {
        enumerableTParameter,
        writerParameter,
        // passing in this expression correctly would produce the weird error in some cases as described above
        writeActionExpression
    }
);

// make an expression to invoke the method
var methodCallExpressionV2 = Expression.Call(
    instance: null, // static
    method: writeCollectionElementsMethod,
    arguments: new[] {
        enumerableTParameter,
        writerParameter,
        // this did not cause the bug
        Expression.Constant(writeActionExpression.Compile())
    }
);

ただし、すべての式を個別にコンパイルするのは好きではなかったため、WriteCollectionElements関数を完全に廃止し、Expression.Loop、Expression.Breakなどを介してforeachループを動的に作成することになりました。

したがって、私はもはやブロックされていませんが、それでも非常に好奇心が強いです。

4

1 に答える 1

1

アクションを C# で手動でビルドすると、resharper は Lambda1 と Lambda2 がクローシュア内の変数を暗黙的にキャプチャすることについて不平を言います

Action<IWriter, int> lambda4 = ( (IWriter writer, int length) => writer.BeginWritingCollection(length));
Action<IWriter, int> lambda3 = ( (IWriter writer, int value) => writer.WriteInt(value));
Action<IWriter, int[]> lambda2 = ( (IWriter writer, int[] value) =>
   {
      lambda4(writer, ((IEnumerable<int>) value).Count());
      WriteCollectionElements((IEnumerable<int>)value, writer, lambda3);
   });
Action<IWriter, TheData> lambda1 = ((writer, data) =>
   {
      lambda2(writer, data.a);
      lambda3(writer, data.b);
   });
class TheData { int[] a; int b; }

この場合、resharper は次のように述べています:
lambda2 式の
"暗黙的にキャプチャされたクロージャ: lambda2" "ラムダ 1 式の "暗黙的にキャプチャされたクロージャ: lambda4"

これについての説明はherehereです。WriteCollectionElements への行を削除すると、警告は消えます。基本的に、JIT コンパイルは内部式呼び出し用のラッパー クラスを作成し、ライターの VALUES と匿名型をキャプチャして、BeginWritingCollection のアクションを WriteCollectionElements 静的メソッドに渡します。

解決策は、ステートメントを lambda2 から lambda1 にインライン化することです。

Action<IWriter, int> lambda4 = ( (IWriter writer, int length) => writer.BeginWritingCollection(length));
Action<IWriter, int> lambda3 = ( (IWriter writer, int value) => writer.WriteInt(value));
Action<IWriter, TheData> lambda1 = ((writer, data) =>
   {
      lambda4(writer, ((IEnumerable<int>) value.a).Count());
      WriteCollectionElements((IEnumerable<int>)value.a, writer, lambda3);
      lambda3(writer, data.b);
   });
class TheData { int[] a; int b; }
于 2012-09-25T03:19:06.903 に答える