c# - 奇怪的异常编译动态构建表达式

标签 c# linq c#-4.0 expression-trees

我正在使用 System.Ling.Expressions API 创建和编译表达式。编译工作正常,但在某些情况下,我在运行已编译的 lambda 时遇到无法解释的 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)
}

异常是在对 #Lambda3 的调用中抛出的,该调用从 WriteCollectionElements 中重复调用。 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都是非空的。我传递给已编译 lambda 的参数是:

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)
}

我在 Windows 7 VS Express C# 2010 上运行 .NET Framework 4(至少这是我的构建目标)。

有没有人知道可能出了什么问题或尝试调试的后续步骤?如果有帮助,我很乐意发布更多信息。

编辑:从那以后(据我所知)我找到了绕过这个错误的方法,尽管我还没有更深入地了解它发生的原因。在生成我上面发布的表达式的代码中,我有以下内容:

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 循环。

于是,我不再被屏蔽,但还是很好奇。

最佳答案

如果您在 C# 中手动构建操作,resharper 会提示 Lambda1 和 Lambda2 在 clousure 中隐式捕获变量

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”
lambda1 表达式上的“隐式捕获闭包:lambda4”

对此的解释是herehere .如果删除 WriteCollectionElements 行,则警告消失。本质上,JIT 编译为内部表达式调用创建一个包装类,捕获编写器和匿名类型的值,以便将 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; }

关于c# - 奇怪的异常编译动态构建表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12280852/

相关文章:

c# - 静态方法与实例方法,多线程,性能

c# - 为什么我不应该总是使用 ICollection 而不是 IEnumerable?

c# - 通过 TCP 协议(protocol)将文件发送到动态端口

c++ - 如何将指针函数从 C++ 导出到 C#?

c# - 对从 Linq 查询访问私有(private)变量感到困惑

c# - wcf 与 C# 网络编程

c# - CRM 2011 自定义工作流程从创建的记录触发器访问数据

asp.net-mvc - Entity Framework 不将值插入可为空的日期时间字段

LINQ 到实体 : Multiple join conditions

c# - asp.net中如何使用线程?