C#:在编译表达式时,已经添加了具有相同键的项
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2105498/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C#: An item with the same key has already been added, when compiling expression
提问by Svish
Ok, here's a tricky one. Hopefully there is an expression guru here who can spot what I am doing wrong here, cause I am just not getting it.
好的,这是一个棘手的问题。希望这里有一个表达大师可以发现我在这里做错了什么,因为我只是不明白。
I am building up expressions that I use to filter queries. To ease that process I have a couple of Expression<Func<T, bool>>
extension methods that makes my code cleaner and so far they have been working nicely. I have written tests for all of them except one, which I wrote one for today. And that test fails completely with an ArgumentException
with a longstack trace. And I just don't get it. Especially since I have been using that method for a while successfully in my queries!
我正在构建用于过滤查询的表达式。为了简化这个过程,我有几个Expression<Func<T, bool>>
扩展方法可以使我的代码更清晰,到目前为止它们一直运行良好。除了今天我写了一个测试之外,我已经为所有这些测试编写了测试。并且该测试完全失败并ArgumentException
带有很长的堆栈跟踪。而我就是不明白。特别是因为我已经在查询中成功使用了一段时间!
Anyways, here is the stack trace I get when running the test:
无论如何,这是我在运行测试时得到的堆栈跟踪:
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()
The test itself looks like the following, an it fails at the Compile statement:
测试本身如下所示,它在 Compile 语句中失败:
[Test]
public void WhereWithin_CollectionIsFilteredAsExpected()
{
var range = new[] { Range.Create(2, 7), Range.Create(15, 18) };
var predicate = Predicate
.Create<int>(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 }));
}
I just don't understand the error message. I thought it might have to do with the fact that I always use x
as the parameter name, but didn't seem to help when I tried to swap them around. What makes it even weirder to me is that I have been using this exact method for a while already in bigger Linq2Sql queries and they have always worked nicely. So in my test I tried to not compile the expression and use AsQueryable
so I could use it on that instead. But that just made the exception occur on the ToArray
instead. What is going on here? How can I fix this?
我只是不明白错误信息。我认为这可能与我总是x
用作参数名称的事实有关,但是当我尝试交换它们时似乎没有帮助。对我来说更奇怪的是,我已经在更大的 Linq2Sql 查询中使用这种确切的方法有一段时间了,而且它们一直运行良好。所以在我的测试中,我试图不编译表达式并AsQueryable
使用它,以便我可以在它上面使用它。但这只是使异常发生在ToArray
相反的地方。这里发生了什么?我怎样才能解决这个问题?
You can find the offending and annoying code in the zip file below the line:
您可以在该行下方的 zip 文件中找到令人反感和烦人的代码:
Note:I had posted some of the related code here, but after some comments I decided to extract the code into it's own project which shows the exception more clearly. And more importantly, that can be run, compiled and debugged.
注意:我已经在这里发布了一些相关的代码,但经过一些评论后,我决定将代码提取到它自己的项目中,这样可以更清楚地显示异常。更重要的是,它可以运行、编译和调试。
Update:Simplified the example project even further with some of the suggestions from @Mark. Like removing the range class and instead just hard coding single constant range. Also added another example where using the exact same method actually works fine. So, using the AndWithin method makes the app crash, while using the WhereWithin method actually works fine. I feel pretty much clueless!
更新:使用@Mark 的一些建议进一步简化了示例项目。就像删除范围类,而只是硬编码单个常量范围。还添加了另一个示例,其中使用完全相同的方法实际上可以正常工作。因此,使用 AndWithin 方法会使应用程序崩溃,而使用 WhereWithin 方法实际上可以正常工作。我觉得很无知!
- ExpressionCuriosity.zip(Updated)
回答by Mark Byers
This is not an answer, but I hope it will help someone find the answer. I've simplified the code further so that it is just one single file and still fails in the same way. I have renamed the variables so that "x" is not used twice. I have removed the Range class and replaced it with hardcoded constants 0 and 1.
这不是答案,但我希望它能帮助有人找到答案。我进一步简化了代码,使其只是一个文件,并且仍然以相同的方式失败。我重命名了变量,这样“x”就不会被使用两次。我已经删除了 Range 类并将其替换为硬编码常量 0 和 1。
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
static Expression<Func<int, bool>> And(Expression<Func<int, bool>> first,
Expression<Func<int, bool>> second)
{
var x = Expression.Parameter(typeof(int), "x");
var body = Expression.AndAlso(Expression.Invoke(first, x), Expression.Invoke(second, x));
return Expression.Lambda<Func<int, bool>>(body, x);
}
static Expression<Func<int, bool>> GetPredicateFor(Expression<Func<int, int>> 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<Func<int, bool>>(body, param);
}
static void Main()
{
Expression<Func<int, bool>> predicate = a => true;
predicate = And(predicate, GetPredicateFor(b => b)); // Comment out this line and it will run without error
var z = predicate.Compile();
}
}
The expression looks like this in the debugger:
调试器中的表达式如下所示:
x => (Invoke(a => True,x) && Invoke(y => ((Invoke(b => b,y) >= 0) && (Invoke(b => b,y) <= 1)),x))
Update: I've simplified it down to about the most simple it can be while still throwing the same exception:
更新:我已经将它简化为最简单的,同时仍然抛出相同的异常:
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, bool>> selector = b => true;
ParameterExpression param = Expression.Parameter(typeof(int), "y");
InvocationExpression member = Expression.Invoke(selector, param);
Expression body = Expression.AndAlso(member, member);
Expression<Func<int, bool>> predicate = Expression.Lambda<Func<int, bool>>(body, param);
var z = predicate.Compile();
}
}
回答by Nick Craver
I refactored your methods a bit to make the compiler a bit happier:
我重构了你的方法,让编译器更开心一点:
public static Expression<Func<TSubject, bool>> AndWithin<TSubject, TField>(
this Expression<Func<TSubject, bool>> original,
IEnumerable<Range<TField>> range, Expression<Func<TSubject, TField>> field) where TField : IComparable<TField>
{
return original.And(range.GetPredicateFor(field));
}
static Expression<Func<TSource, bool>> GetPredicateFor<TSource, TValue>
(this IEnumerable<Range<TValue>> range, Expression<Func<TSource, TValue>> selector) where TValue : IComparable<TValue>
{
var param = Expression.Parameter(typeof(TSource), "x");
if (range == null || !range.Any())
return Expression.Lambda<Func<TSource, bool>>(Expression.Constant(false), param);
Expression body = null;
foreach (var r in range)
{
Expression<Func<TValue, TValue, TValue, bool>> 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<Func<TSource, bool>>(body, param);
}
Both have the added restriction of IComparable<TValue>
(the only change to the first method).
两者都有附加限制IComparable<TValue>
(对第一种方法的唯一更改)。
In the second, I'm doing the comparison via a Func
Expression implementation, notice that the func is created inside the loop...it's the second addition of this (what it thinks is the same...) expression in the old method that's blowing up.
在第二个中,我通过Func
Expression 实现进行比较,请注意 func 是在循环内创建的……这是旧方法中的第二次添加(它认为是相同的……)表达式吹起来。
Disclaimer: I still don't fully understand why your previous method didn't work, but this alternative approach bypasses the problem. Let me know if this isn't what you're after and we'll try something else.
免责声明:我仍然不完全理解为什么您之前的方法不起作用,但是这种替代方法绕过了这个问题。如果这不是您想要的,请告诉我,我们会尝试其他方法。
Also, kudos on ASKINGa question well, a sample project is exemplary.
此外,在荣誉ASKING一个问题提得好,一个示例项目是示范性的。