C#:编译表达式时,已添加具有相同键的项

好的,这是一个棘手的问题。 希望这里有一位表达大师可以发现我在这里做错了什么,因为我只是没有得到它。

我正在构建用于过滤查询的表达式。 为了简化这个过程,我有一些Expression<Func>扩展方法,这些方法使我的代码更清晰,到目前为止它们已经很好地工作了。 我已经为所有人编写了测试,除了一个,我今天写了一个。 并且该测试完全失败,带有堆栈跟踪的ArgumentException 。 我只是不明白。 特别是因为我在查询中成功使用了该方法一段时间了!

无论如何,这是运行测试时得到的堆栈跟踪:

 failed: System.ArgumentException : An item with the same key has already been added. at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) at System.Linq.Expressions.ExpressionCompiler.PrepareInitLocal(ILGenerator gen, ParameterExpression p) at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask) at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b) at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedOrElse(ILGenerator gen, BinaryExpression b) at System.Linq.Expressions.ExpressionCompiler.GenerateOrElse(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask) at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b) at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask) at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask) at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda) at System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda) at System.Linq.Expressions.Expression`1.Compile() PredicateTests.cs(257,0): at Namespace.ExpressionExtensionsTests.WhereWithin_CollectionIsFilteredAsExpected() 

测试本身如下所示,它在Compile语句中失败:

 [Test] public void WhereWithin_CollectionIsFilteredAsExpected() { var range = new[] { Range.Create(2, 7), Range.Create(15, 18) }; var predicate = Predicate .Create(x => x % 2 == 0) .AndWithin(range, x => x) .Compile(); var actual = Enumerable.Range(0, 20) .Where(predicate) .ToArray(); Assert.That(actual, Is.EqualTo(new[] { 2, 4, 6, 16, 18 })); } 

我只是不明白错误信息。 我认为这可能与我总是使用x作为参数名称的事实有关,但是当我尝试交换它们时似乎没有帮助。 让我更奇怪的是,我已经在更大的Linq2Sql查询中使用了这种精确的方法,并且它们总是很好地工作。 所以在我的测试中,我试图不编译表达式并使用AsQueryable所以我可以在其上使用它。 但这只是在ToArray上发生exception。 这里发生了什么? 我怎样才能解决这个问题?

您可以在以下行的zip文件中找到令人讨厌且烦人的代码:


注意:我在这里发布了一些相关的代码,但经过一些评论后,我决定将代码提取到自己的项目中,更清楚地显示exception。 更重要的是,可以运行,编译和调试。

  • ExpressionCuriosity.zip

更新:使用@Mark的一些建议进一步简化了示例项目。 像删除范围类,而只是硬编码单个恒定范围。 还添加了另一个例子,使用完全相同的方法实际上工作正常。 因此,使用AndWithin方法会使应用程序崩溃,而使用WhereWithin方法实际上可以正常工作。 我觉得很无能为力!

  • ExpressionCuriosity.zip (已更新)

我重构了你的方法,使编译器更快乐:

 public static Expression> AndWithin( this Expression> original, IEnumerable> range, Expression> field) where TField : IComparable { return original.And(range.GetPredicateFor(field)); } static Expression> GetPredicateFor (this IEnumerable> range, Expression> selector) where TValue : IComparable { var param = Expression.Parameter(typeof(TSource), "x"); if (range == null || !range.Any()) return Expression.Lambda>(Expression.Constant(false), param); Expression body = null; foreach (var r in range) { Expression> BT = (val, min, max) => val.CompareTo(min) >= 0 && val.CompareTo(max) <= 0; var newPart = Expression.Invoke(BT, param, Expression.Constant(r.Start, typeof(TValue)), Expression.Constant(r.End, typeof(TValue))); body = body == null ? newPart : (Expression)Expression.OrElse(body, newPart); } return Expression.Lambda>(body, param); } 

两者都有IComparable的附加限制(对第一种方法的唯一改变)。

在第二个中,我通过Func Expression实现进行比较,注意func是在循环内部创建的……它是旧方法中第二个添加它(它认为是相同的……)表达式那真是太过分了。

免责声明:我仍然不完全理解为什么你以前的方法不起作用,但这种替代方法绕过了问题。 让我知道如果这不是你想要的,我们会尝试其他的东西。

另外,一个问题很好的问题,一个示例项目是示范性的。

这不是答案,但我希望它能帮助别人找到答案。 我已经进一步简化了代码,因此它只是一个文件,但仍然以同样的方式失败。 我已重命名变量,以便“x”不会被使用两次。 我删除了Range类,并用硬编码的常量0和1替换它。

 using System; using System.Linq; using System.Linq.Expressions; class Program { static Expression> And(Expression> first, Expression> second) { var x = Expression.Parameter(typeof(int), "x"); var body = Expression.AndAlso(Expression.Invoke(first, x), Expression.Invoke(second, x)); return Expression.Lambda>(body, x); } static Expression> GetPredicateFor(Expression> selector) { var param = Expression.Parameter(typeof(int), "y"); var member = Expression.Invoke(selector, param); Expression body = Expression.AndAlso( Expression.GreaterThanOrEqual(member, Expression.Constant(0, typeof(int))), Expression.LessThanOrEqual(member, Expression.Constant(1, typeof(int)))); return Expression.Lambda>(body, param); } static void Main() { Expression> predicate = a => true; predicate = And(predicate, GetPredicateFor(b => b)); // Comment out this line and it will run without error var z = predicate.Compile(); } } 

表达式在调试器中如下所示:

 x => (Invoke(a => True,x) && Invoke(y => ((Invoke(b => b,y) >= 0) && (Invoke(b => b,y) <= 1)),x)) 

更新 :我已经将它简化为最简单的事情,同时仍然抛出相同的exception:

 using System; using System.Linq; using System.Linq.Expressions; class Program { static void Main() { Expression> selector = b => true; ParameterExpression param = Expression.Parameter(typeof(int), "y"); InvocationExpression member = Expression.Invoke(selector, param); Expression body = Expression.AndAlso(member, member); Expression> predicate = Expression.Lambda>(body, param); var z = predicate.Compile(); } }