最近遇到这样的问题:
考虑以下代码将打印什么数字:
class Program
{
static void Main(string[] args)
{
int[] numbers = { 1, 3, 5, 7, 9 };
int threshold = 6;
var query = from value in numbers where value >= threshold select value;
threshold = 3;
var result = query.ToList();
result.ForEach(Console.WriteLine);
Console.ReadLine();
}
}
答案:3, 5, 7, 9
这让我很惊讶。我认为 threshold
值将在查询构造时放入堆栈,稍后在执行时,该数字将被拉回并在条件中使用..这没有发生。
另一种情况(numbers
在执行前设置为null
):
static void Main(string[] args)
{
int[] numbers = { 1, 3, 5, 7, 9 };
int threshold = 6;
var query = from value in numbers where value >= threshold select value;
threshold = 3;
numbers = null;
var result = query.ToList();
...
}
好像对查询没有影响。它打印出与前面示例中完全相同的答案。
任何人都可以帮助我了解幕后真正发生的事情吗?为什么更改 threshold
会对查询执行产生影响,而更改 numbers
则不会?
最佳答案
您的查询可以在方法语法中这样写:
var query = numbers.Where(value => value >= threshold);
或者:
Func<int, bool> predicate = delegate(value) {
return value >= threshold;
}
IEnumerable<int> query = numbers.Where(predicate);
这些代码片段(包括您自己在查询语法中的查询)都是等价的。
当您像这样展开查询时,您会看到 predicate
是一个 anonymous method threshold
是 closure在那种方法中。这意味着它将在执行时采用该值。编译器将生成一个实际的(非匿名的)方法来处理这个问题。该方法在声明时不会执行,但在枚举query
时针对每个项目执行(执行是延迟)。由于枚举发生在 threshold
的值更改之后(并且 threshold
是一个闭包),因此使用新值。
当您将 numbers
设置为 null
时,您将引用设置为无处可去,但该对象仍然存在。 Where
(并在 query
中引用)返回的 IEnumerable
仍然引用它并且初始引用为 null< 无关紧要
现在。
这解释了行为:numbers
和 threshold
在延迟执行中扮演不同的角色。 numbers
是对枚举数组的引用,而 threshold
是局部变量,其范围“转发”给匿名方法。
扩展,第 1 部分:在枚举期间修改闭包
当您替换该行时,您可以将您的示例更进一步...
var result = query.ToList();
...与:
List<int> result = new List<int>();
foreach(int value in query) {
threshold = 8;
result.Add(value);
}
您正在做的是在数组迭代期间更改 threshold
的值。当您第一次进入循环体时(当 value
为 3 时),您将阈值更改为 8,这意味着将跳过值 5 和 7,并添加下一个值列表是 9。原因是 threshold
的值将在每次迭代中再次计算,并且将使用 then 有效值。由于阈值已更改为 8,因此数字 5 和 7 不再评估为大于或等于。
扩展,第 2 部分: Entity Framework 不同
让事情变得更复杂的是,当您使用 LINQ 提供程序创建与原始查询不同的查询然后执行它时,情况会略有不同。最常见的示例是 Entity Framework (EF) 和 LINQ2SQL(现在大部分已被 EF 取代)。这些提供程序在枚举之前根据原始查询创建 SQL 查询。由于这次闭包的值只被评估一次(它实际上不是闭包,因为编译器生成的是表达式树而不是匿名方法),枚举期间threshold
的变化对结果没有影响。这些更改发生在查询提交到数据库之后。
从中得出的教训是,您必须始终了解您使用的是哪种 LINQ,了解其内部工作原理是一个优势。
关于c# - 延迟的 LINQ 查询执行实际上是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47379362/