C#:编译表达式时,已经添加了具有相同键的项目 [英] C#: An item with the same key has already been added, when compiling expression

查看:139
本文介绍了C#:编译表达式时,已经添加了具有相同键的项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我正在建立我用来过滤的表达式查询。为了简化这个过程,我有几个 Expression< Func< T,bool>> 扩展方法,使我的代码更清洁,迄今为止他们一直在很好地工作。我为所有人写了测试,除了一个,我今天写了一个。而且该测试使用堆栈跟踪的 ArgumentException 完全失败。我只是不明白。特别是因为我在我的查询中一直使用该方法一段时间!



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

 失败:System.ArgumentException:已添加具有相同键的项目。 
在System.ThrowHelper.ThrowArgumentException(ExceptionResource资源)
在System.Collections.Generic.Dictionary`2.Insert(TKey键,TValue值,Boolean添加)
在System.Linq.Expressions System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen,InvocationExpression invoke,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.Generate( ILGenerator gen,Expression node,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen,BinaryExpression b,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen ,ExpressionNode,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen,BinaryExpression b)
在System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen,BinaryExpression b,StackTy pe询问)​​
在System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen,BinaryExpression b,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen,Expression node,StackType ask )
在System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedOrElse(ILGenerator gen,BinaryExpression b)
在System.Linq.Expressions.ExpressionCompiler.GenerateOrElse(ILGenerator gen,BinaryExpression b,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen,BinaryExpression b,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen,Expression node,StackType ask)
在系统.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen,InvocationExpression invoke,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen,Expression node,StackType ask)
在System.Linq .Expressions.Expre ssgenCompiler.GenerateUnliftedAndAlso(ILGenerator gen,BinaryExpression b)
在System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen,BinaryExpression b,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen,BinaryExpression b,StackType请求)
在System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen,Expression node,StackType ask)
在System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
在System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda)
在System.Linq.Expressions.Expression`1.Compile()
PredicateTests.cs(257,0):at命名空间.ExpressionExtensionsTests.WhereWithin_CollectionIsFilteredAsExpected()

测试本身如下所示,它在编译失败声明:

  [测试] 
public void WhereWithin_CollectionIsFilt eredAsExpected()
{
var range = new [] {Range.Create(2,7),Range.Create(15,18)};

var 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文件中找到令人讨厌且令人讨厌的代码:



< hr>

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








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




解决方案

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

  public static Expression& TSubject,bool>>> AndWithin< TSubject,TField>(
this Expression< Func< TSubject,bool>> original,
IEnumerable< Range< TField>范围,表达&Funk< TSubject,TField>>其中TField:IComparable< TField>
{
return original.And(range.GetPredicateFor(field));
}


static表达式< Func< TSource,bool>> GetPredicateFor< TSource,TValue>
(此IEnumerable< Range< TValue>范围,表达< Func< TSource,TValue>选择器)其中TValue:IComparable< TValue&
{
var param = Expression.Parameter(typeof(TSource),x);

if(range == null ||!range.Any())
return Expression.Lambda
表达式body = null;
foreach(范围内的var r)
{
表达式< 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);
}

返回Expression.Lambda }

两者都有 IComparable< TValue> (第一个方法的唯一变化)。



第二个,我通过一个 Func 表达式实现,请注意,func是在循环中创建的...它是第二个添加(它认为是相同的...)在旧方法中的表达式。 / p>

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



另外,在 ASKING 一个示例项目就是示范。


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 long stack trace. And I just don't get it. Especially since I have been using that method for a while successfully in my queries!

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:

[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?

You can find the offending and annoying code in the zip file below the line:


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!

解决方案

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).

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.

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 ASKING a question well, a sample project is exemplary.

这篇关于C#:编译表达式时,已经添加了具有相同键的项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆