我正在使用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都是非null的。我传递给已编译的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循环。
因此,我不再受阻,但仍然非常好奇。
答案 0 :(得分:1)
如果您在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”
对此的解释是here和here。如果删除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; }