50

Well, the following code is self-explaining; I want to combine two expressions into one using And operator. The last line causes rune-time the error:

Additional information: variable 'y' of type 'System.String' referenced from scope '', but it is not defined

Code:

Expression<Func<string, bool>> e1 = y => y.Length < 100;
Expression<Func<string, bool>> e2 = y => y.Length < 200;

var e3 = Expression.And(e1.Body, e2.Body);

var e4 = Expression.Lambda<Func<string, bool>>(e3, e1.Parameters.ToArray());
e4.Compile(); // <--- causes run-time error
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Hans
  • 2,674
  • 4
  • 25
  • 48
  • 11
    Don't claim code is self-explaining. Explain what you expect it to do. Do you want `e1` and `e2` to be `and`ed and to operate on one and the same input parameter? – CodeCaster May 31 '15 at 11:42
  • 1
    The variable `y` used in `e2` isn't defined - it may have the same name as the one in `e1`, but that's not how they're matched. – Charles Mager May 31 '15 at 11:42
  • 1
    @CodeCaster What you asked about is exactly mentioned in the question. Thank you. – Hans May 31 '15 at 12:04
  • 1
    Hans, no, it isn't. You only mention "combine", you don't mention you want both `y` parameters to refer to the same parameter. Notice the "and" in that comment. You're welcome. – CodeCaster May 31 '15 at 12:05

3 Answers3

28

As indicated in the other answer, you have two expressions where both have a parameter named y. Those don't automatically relate to each other.

To properly compile your expression, you need to specify both source expression's parameters:

Expression<Func<string, bool>> e1 = (y => y.Length > 0);
Expression<Func<string, bool>> e2 = (y => y.Length < 5);

var e3 = Expression.And(e1.Body, e2.Body);

// (string, string) by adding both expressions' parameters.
var e4 = Expression.Lambda<Func<string, string, bool>>(e3, new[] 
{ 
    e1.Parameters[0], 
    e2.Parameters[0] 
});

Func<string, string, bool> compiledExpression = e4.Compile();

bool result = compiledExpression("Foo", "Foo");

Of course, you'd want an expression that combines both expressions with only one parameter. You can rebuild the expressions like this:

ParameterExpression param = Expression.Parameter(typeof(string), "y");
var lengthPropertyExpression = Expression.Property(param, "Length");

var e1 = Expression.GreaterThan(lengthPropertyExpression, Expression.Constant(0));
var e2 = Expression.LessThan(lengthPropertyExpression, Expression.Constant(5));

var e3 = Expression.AndAlso(e1, e2);

var e4 = Expression.Lambda<Func<string, bool>>(e3, new[] { param });

Func<string, bool> compiledExpression = e4.Compile();

bool result = compiledExpression("Foo");

As for your comment that you don't want to rebuild the expression, but do it on an existing expression's body and parameters: this works using ExpressionRewriter from Combining two lambda expressions in c# and AndAlso from Replacing the parameter name in the Body of an Expression:

Expression<Func<string, bool>> e1 = (y => y.Length > 0);
Expression<Func<string, bool>> e2 = (z => z.Length < 10);

var e3 = ParameterReplacer.AndAlso<string>(e1, e2);

Func<string, bool> compiledExpression = e3.Compile();

bool result = compiledExpression("Foo");
Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 1
    Thanks, my problem is that the two expressions are passed into a combining method and I'm not aware of the body and really I don't want to get involved, although I asked a simpler question. – Hans May 31 '15 at 12:11
24

The problem is that parameter expression objects that represents variable y in expressions e1 and e2 are different. The fact that the two variables are named the same and have the same type does not matter: e1.Parameters.First() and e2.Parameters.First() is not the same object.

This causes the problem that you see: only e1's parameter y is available to Lambda<>, while e2's parameter y is out of scope.

To fix this problem use Expression APIs to create e1 and e2. This way you would be able to share the parameter expression across them, thus eliminating the problem of scope.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 2
    What kind of API should I use, would you please help? – Hans May 31 '15 at 12:09
  • 1
    @Hans Use the same API that you are using in the second half the way the other answer is showing. I assume that you are building these expressions in your code. If this assumption is incorrect, and someone else is passing you expressions `e1` and `e2`, you need to replace parameters by walking the expression tree (the last part of the other answer explains how to do that, too). – Sergey Kalinichenko May 31 '15 at 12:42
20

Thanks everybody collaborated.

As @dasblinkenlight pointed out the two parameters in the two expressions are not the same. Reason? Well, it is the compiler trick. When compiling, it creates a class for each expression and name each parameter something like xxx1, xxx2,... completely different from the original names.

And the answer for .Net 4.0+:

How to Combine two lambdas

Vahid Farahmandian
  • 6,081
  • 7
  • 42
  • 62
Hans
  • 2,674
  • 4
  • 25
  • 48