您的查询可以用方法语法这样编写:
var query = numbers.Where(value => value >= threshold);
Or:
Func<int, bool> predicate = delegate(value) {
return value >= threshold;
}
IEnumerable<int> query = numbers.Where(predicate);
这些代码段(包括查询语法中您自己的查询)都是等效的。
当您像这样展开查询时,您会看到predicate
is an 匿名方法 https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-methods and threshold
is a closure https://stackoverflow.com/questions/428617/what-are-closures-in-net在那个方法中。这意味着它将采用执行时的值。编译器将生成一个实际的(非匿名)方法来处理这个问题。该方法不会在声明时执行,而是针对每个项目时执行query
被枚举(执行是deferred)。由于枚举发生在值之后threshold
被改变(并且threshold
是一个闭包),使用新值。
当你设置numbers
to null
,您将引用设置为无处,但该对象仍然存在。这IEnumerable
由返回Where
(并在中引用query
)仍然引用它,并且初始引用是并不重要null
now.
这解释了这种行为:numbers
and threshold
在延迟执行中扮演不同的角色。numbers
是对枚举数组的引用,而threshold
是一个局部变量,其范围“转发”到匿名方法。
扩展,第 1 部分:枚举期间闭包的修改
当您更换线路时,您可以将示例更进一步......
var result = query.ToList();
...with:
List<int> result = new List<int>();
foreach(int value in query) {
threshold = 8;
result.Add(value);
}
你正在做的是改变的价值threshold
during数组的迭代。当你第一次点击循环体时(当value
是 3),您将阈值更改为 8,这意味着值 5 和 7 将被跳过,下一个要添加到列表中的值是 9。原因是threshold
将在每次迭代时再次评估,并且then将使用有效值。由于阈值已更改为 8,因此数字 5 和 7 不再被评估为大于或等于。
扩展,第 2 部分:实体框架的不同
让事情变得更复杂的是,当您使用 LINQ 提供程序创建与原始查询不同的查询然后执行它时,情况会略有不同。最常见的示例是实体框架 (EF) 和 LINQ2SQL(现已基本被 EF 取代)。这些提供程序根据原始查询创建 SQL 查询枚举之前。由于这次闭包的值只被计算一次(它实际上不是一个闭包,因为编译器生成一个表达式树而不是一个匿名方法),所以变化threshold
枚举期间对结果没有影响。这些更改发生在查询提交到数据库之后。
从中得到的教训是,您必须始终了解您正在使用哪种 LINQ 风格,并且了解其内部工作原理是一个优势。