c# - 延迟的 LINQ 查询执行实际上是如何工作的?

标签 c# linq

最近遇到这样的问题: 考虑以下代码将打印什么数字:

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 thresholdclosure在那种方法中。这意味着它将在执行时采用该值。编译器将生成一个实际的(非匿名的)方法来处理这个问题。该方法在声明时不会执行,但在枚举query 时针对每个项目执行(执行是延迟)。由于枚举发生在 threshold 的值更改之后(并且 threshold 是一个闭包),因此使用新值。

当您将 numbers 设置为 null 时,您将引用设置为无处可去,但该对象仍然存在。 Where(并在 query 中引用)返回的 IEnumerable 仍然引用它并且初始引用为 null< 无关紧要现在。

这解释了行为:numbersthreshold 在延迟执行中扮演不同的角色。 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/

相关文章:

c# - 如何在 where 子句内的可枚举条件上使用 if 条件

c# - 如何读取和修改 XML 文件中的数据?

c# - 阻止应用程序打开

c# - 动态加载具有相同功能的不同库

c# - 是否可以在运行时将 System.Data.SqlServerCe 4.0.0.0 与版本 4.0.0.1 交换?

c# - LINQ to SQL 和对象生命周期、引用与值

c# - 具有多个 group by 列的 linq 语句不允许 thenby

c# - 编译器错误 : An expression tree may not contain a dynamic operation

c# - 使用 Linq c# 将 List<A> 转换为 List<B>

linq - Linq表达式帮助-INotifyPropertyChanged