变量''的类型''在作用域''中被引用,但未定义。

50

好的,下面的代码很容易理解;我想使用 And 运算符将两个表达式合并为一个。最后一行导致运行时错误:

其他信息:变量 'y' 的类型为 'System.String',在作用域中引用,但未定义

代码:

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

11
不要声称代码自我说明。解释你期望代码执行的操作。你想让 e1e2 进行逻辑与操作,并且作用于同一个输入参数吗? - CodeCaster
1
e2 中使用的变量 y 没有被定义 - 它可能与 e1 中的变量同名,但它们并不匹配。 - Charles Mager
1
@CodeCaster,你所问的正好在问题中提到。谢谢。 - Hans
1
Hans,不是的。你只提到了“合并”,但没有提到你想让两个y参数引用同一个参数。请注意评论中的“和”字。不用谢。 - CodeCaster
4个回答

28

如其他答案所示,您有两个表达式,两个表达式中都有一个名为y的参数。这些并不自动相关。

要正确编译您的表达式,您需要指定两个源表达式的参数:

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");

当然,您希望有一个将两个表达式合并为仅使用一个参数的表达式。您可以按以下方式重建这些表达式:

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");

关于你的评论,你不想重新构建表达式,而是在现有表达式的主体和参数上进行操作:可以使用ExpressionRewriterReplacing the parameter name in the Body of an Expression中的 AndAlso来实现。

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");

1
谢谢,我的问题是这两个表达式被传递到一个组合方法中,我不知道具体实现,而且我也不想深入了解,尽管我问的是一个更简单的问题。 - Hans

24
问题在于表达式 e1e2 中代表变量 y 的参数表达式对象是不同的。这两个变量具有相同的名称和类型并不重要: e1.Parameters.First()e2.Parameters.First() 不是同一个对象。
这导致了您看到的问题:只有 e1 的参数 y 可用于 Lambda<>,而 e2 的参数 y 超出了作用域。
要解决此问题,请使用 Expression API 创建 e1e2。这样,您将能够在它们之间共享参数表达式,从而消除作用域问题。

2
我应该使用哪种API呢?你能帮忙吗? - Hans
1
@Hans 使用与另一个答案所示相同的 API,并在代码中构建这些表达式。我假设你正在构建这些表达式。如果这个假设是不正确的,而其他人正在传递给你表达式 e1e2,那么你需要通过遍历表达式树来替换参数(另一个答案的最后一部分也解释了如何做到这一点)。 - Sergey Kalinichenko

20

感谢所有的合作伙伴。

正如@dasblinkenlight所指出的,两个表达式中的两个参数不相同。原因?这是编译器的技巧。在编译时,为每个表达式创建一个类,并将每个参数命名为xxx1、xxx2等,与原始名称完全不同。

而.Net 4.0+的答案是:

如何组合两个lambda表达式


0
另一个会出现这个错误的情况是在使用带有局部变量的BlockExpression时。 Expression.Block有一个带有variables参数的重载方法,你需要将你的局部变量引用传递给这个参数。
Expression.Block(
    variables: new[] { myLocalVar1, myLocalVar2 },
    expressions: /* some collection of expressions that use myLocalVar1 and myLocalVar2 */
);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接