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

标签 c# exception lambda expression expression-trees

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

我正在构建用于过滤查询的表达式。为了简化这个过程,我有几个 Expression<Func<T, bool>>使我的代码更清晰的扩展方法,到目前为止它们运行良好。我已经为所有的人写了测试,除了一个,我今天写了一个。那个测试完全失败了 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<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 文件中的以下行中找到令人讨厌和烦人的代码:


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


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

最佳答案

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

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 进行比较表达式实现,请注意 func 是在循环内创建的...这是第二次添加这个(它认为是相同的...)表达式在爆炸的旧方法中。

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

此外,提问 问题很好,示例项目堪称典范。

关于C#:编译表达式时,已经添加了具有相同键的项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2105498/

相关文章:

c# - Gecko 在 C# 中的使用 (geckofx)

c# - 刷新列表框项目时出现异常

异常中的Python字符串转换为元组

java - 使用 HttpUrlConnection 获取 java.io.EOFException

amazon-web-services - AWS SAM - 资源之间的循环依赖错误

c# - 有没有一种简单的方法来附加 lambda 并重用 lambda 名称以创建我的 Linq where 条件?

python - lambda 申请: Referencing other rows and columns

c# - 使用提升或非提升模式启动进程

c#如何正确格式化ffmpeg用户输入流?

c# - 在 C# 中格式化属性的 Resharper 支持字段