C#:编译表达式时已经添加了具有相同键的项
Posted
技术标签:
【中文标题】C#:编译表达式时已经添加了具有相同键的项【英文标题】:C#: An item with the same key has already been added, when compiling expression 【发布时间】:2011-01-07 12:14:08 【问题描述】:好的,这是一个棘手的问题。希望这里有一位表达大师可以发现我在这里做错了什么,因为我只是不明白。
我正在构建用于过滤查询的表达式。为了简化这个过程,我有几个Expression<Func<T, bool>>
扩展方法可以让我的代码更简洁,并且到目前为止它们运行良好。我已经为所有这些都写了测试,除了一个,我今天写了一个。该测试完全失败,ArgumentException
带有 long 堆栈跟踪。我就是不明白。特别是因为我在查询中成功地使用了该方法一段时间!
无论如何,这是我在运行测试时得到的堆栈跟踪:
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<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 ));
我只是不明白错误信息。我认为这可能与我总是使用 x
作为参数名称这一事实有关,但是当我尝试交换它们时似乎没有帮助。对我来说更奇怪的是,我已经在更大的 Linq2Sql 查询中使用这种确切的方法一段时间了,而且它们一直运行良好。所以在我的测试中,我尝试不编译表达式并使用AsQueryable
,所以我可以在上面使用它。但这只是在ToArray
上发生了异常。这里发生了什么?我该如何解决这个问题?
您可以在 zip 文件中的以下行中找到令人讨厌和烦人的代码:
注意:我已经在这里发布了一些相关的代码,但是在一些 cmet 之后,我决定将代码提取到它自己的项目中,这样可以更清楚地显示异常。更重要的是,它可以运行、编译和调试。
ExpressionCuriosity.zip更新:利用@Mark 的一些建议进一步简化了示例项目。就像删除范围类,而只是硬编码单个常量范围。还添加了另一个示例,其中使用完全相同的方法实际上可以正常工作。因此,使用 AndWithin 方法会使应用程序崩溃,而使用 WhereWithin 方法实际上可以正常工作。我觉得很无知!
ExpressionCuriosity.zip(更新)【问题讨论】:
Range<TValue>
是你自己写的类吗?
@Mark:是的,它主要是一个有开始和结束的类。虽然我有一系列的扩展方法,它有 equals 被覆盖等等。但在这种情况下,这并不重要。这是一个非常简单的类:)
作为注释,System.Predicate 已经存在,所以命名有些混乱。 msdn.microsoft.com/en-us/library/bfcke1bz.aspx
@recursive:我知道,但想不出更好的。而且因为我从来没有真正使用过那个谓词委托......是的......:p
@Mark:好吧,我不这样做就无法入睡。用最少的代码制作了一个小项目来犯错误。好吧,可能会更少,但是是的。 编辑问题
【参考方案1】:
我稍微重构了你的方法以使编译器更快乐:
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);
两者都有IComparable<TValue>
的附加限制(对第一种方法的唯一更改)。
在第二个中,我通过Func
Expression 实现进行比较,请注意 func 是在循环内创建的……这是第二次添加(它认为是相同的……)旧方法中的表达式正在爆炸。
免责声明:我仍然不完全理解为什么您以前的方法不起作用,但是这种替代方法绕过了问题。如果这不是您想要的,请告诉我,我们会尝试其他方法。
此外,ASKING 很好地提出了一个问题,示例项目堪称典范。
【讨论】:
嗯...不是为你编译的吗?在这里编译...无论如何,是什么让它通过了你? IComparable,还是在表达式中进行比较?在 Linq2Sql 查询中这样做会起作用吗? 你是对的,这使我的示例问题不再崩溃。但是,它不可用,因为我无法使用比较方法,因为 LinqToSql 提供程序不支持它=/(感谢您的荣誉:p) @Svish:我们的供应商支持它,您只是无法提供自己的比较器,您尝试此操作时遇到什么错误? 哦,所以我不能使用默认比较器之类的? @Svish:你可以,这是唯一你可以的。字符串的情况类似,看这个:msdn.microsoft.com/en-us/library/bb882672.aspx 你可以使用 String.Compare(string1, string2),但没有其他重载工作,因为在 SQL 中它只是被翻译为 COLUMN_A > COLUMN_B,与任何其他 .Compare 相同,除非有一个人熟悉的例外。【参考方案2】:这不是一个答案,但我希望它能帮助某人找到答案。我进一步简化了代码,使它只是一个文件,并且仍然以同样的方式失败。我已经重命名了变量,这样“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();
表达式在调试器中如下所示:
x => (Invoke(a => True,x) && Invoke(y => ((Invoke(b => b,y) >= 0) && (Invoke(b => b,y) <= 1)),x))
更新:我已将其简化到最简单的程度,同时仍然抛出相同的异常:
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();
【讨论】:
用你的一些简化创建了一个更新的项目。请检查一下:) 请注意,简化代码(只有 6 行的版本)在 .NET 4.0 中有效,但在 .NET 3.5 中无效,这表明存在错误在 .NET 3.5 中已在 4.0 中修复。此外,原始代码在 .NET 4.0 中不起作用,这可能意味着 .NET 4.0 中仍然存在错误,您或许应该将此作为可能的错误报告给 Microsoft。 嗯...您如何将此作为错误报告给 Microsoft?从来没有真正理解过......我想它与连接网站有关,但从未弄清楚丛林 =/ 如果你确实选择了这条路线,请偶尔回来提供状态更新(或错误跟踪票),因为我对结果如何非常感兴趣,并根据投票的数量来判断/stars 你的问题得到了,其他几个也是。以上是关于C#:编译表达式时已经添加了具有相同键的项的主要内容,如果未能解决你的问题,请参考以下文章