问题
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
回答1:
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.
回答2:
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");
回答3:
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
来源:https://stackoverflow.com/questions/30556911/variable-of-type-referenced-from-scope-but-it-is-not-defined