C#: An item with the same key has already been added, when compiling expression

后端 未结 2 1723
攒了一身酷
攒了一身酷 2021-02-12 15:08

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 expression

相关标签:
2条回答
  • 2021-02-12 15:48

    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.

    0 讨论(0)
  • 2021-02-12 15:55

    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.

    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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题