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&lt;TValue&gt; 是你自己写的类吗? @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&lt;TValue&gt;的附加限制(对第一种方法的唯一更改)。

在第二个中,我通过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#:编译表达式时已经添加了具有相同键的项的主要内容,如果未能解决你的问题,请参考以下文章

[解决问题]未知的生成错误“已添加了具有相同键的项”(SVN进行更新后冲突,冲突解决等操作导致,并且无法定位到代码)

C#如何创建具有多个相同键的字典? [复制]

已添加具有相同密钥的项目 - 仅处于发布模式

已添加具有相同键的项目

C#使用左右连接创建3个表的lambda表达式

控制器中的ObjectStateManager错误中已经存在具有相同键的对象