Dividing an Expression in C# using Expression.AndAlso() causes an Exception

前端 未结 1 1091
清歌不尽
清歌不尽 2021-01-23 20:29

On my project written in C#, I\'ve found a HUGE predicate that is used in this method of linq :

public static IQueryable Where(this         


        
相关标签:
1条回答
  • 2021-01-23 21:27

    Because where you see a single c parameter, in truth there are two different c parameters (let's call them c1 and c2). So when you merge the two expressions you have:

    c1 => c1.Something && c2.SomethingElse;
    

    And the CLR gets angry because it can't find the c2.

    Worse, as you wrote your code, you have three c!

    c3 => c1.Something && c2.SomethingElse
    

    This because you rebuild expr1("a string") twice (in the Expression.AndAlso(expr1("a string").Body and in the expr1("a string").Parameters[0])!

    You should have saved it!

    var temp1 = expr1("a string");
    var temp2 = expr2("same string");
    
    var expr = Expression.AndAlso(temp1.Body, temp2.Body);
    
    // now fix the expr so that it uses the parameters of temp1
    
    return Expression.Lambda<Func<TEntity, bool>>(expr, temp1.Parameters);
    

    To give a clear example:

    var temp1a = expr1("a string");
    var temp1b = expr1("a string");
    var temp2 = expr2("same string");
    
    Console.WriteLine(temp1a.Parameters[0] == temp1b.Parameters[0]); // False
    Console.WriteLine(temp1a.Parameters[0] == temp2.Parameters[0]); // False
    

    Now... My version of parameter replacer:

    public class SimpleParameterReplacer : ExpressionVisitor
    {
        public readonly ReadOnlyCollection<ParameterExpression> From;
        public readonly ReadOnlyCollection<ParameterExpression> To;
    
        public SimpleParameterReplacer(ParameterExpression from, ParameterExpression to)
            : this(new[] { from }, new[] { to })
        {
        }
    
        public SimpleParameterReplacer(IList<ParameterExpression> from, IList<ParameterExpression> to)
        {
            if (from == null || from.Any(x => x == null))
            {
                throw new ArgumentNullException("from");
            }
    
            if (to == null || to.Any(x => x == null))
            {
                throw new ArgumentNullException("to");
            }
    
            if (from.Count != to.Count)
            {
                throw new ArgumentException("to");
            }
    
            // Note that we should really clone from and to... But we will
            // ignore this!
            From = new ReadOnlyCollection<ParameterExpression>(from);
            To = new ReadOnlyCollection<ParameterExpression>(to);
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            int ix = From.IndexOf(node);
    
            if (ix != -1)
            {
                node = To[ix];
            }
    
            return base.VisitParameter(node);
        }
    }
    

    You can use to change a single parameter or a parameter array... You can use it like:

    var temp1 = expr1("a string");
    var temp2 = expr2("same string");
    
    var expr = Expression.AndAlso(temp1.Body, temp2.Body);
    expr = new SimpleParameterReplacer(temp2.Parameters, temp1.Parameters).Visit(expr);
    
    return Expression.Lambda<Func<TEntity, bool>>(expr, temp1.Parameters);
    
    0 讨论(0)
提交回复
热议问题