LINQ - 延迟执行

创建时间:
2014-02-28 11:13
最近更新:
2018-10-06 17:09

Other

  • 延迟 delay
  • 与 "virtual - Entity Framework 延迟加载" 无关。

Brief

LINQ 中大部分查询运算符都有一个非常重要的特性: 延迟执行。这意味着,他们不是在查询创建的时候执行,而是在遍历的时候执行 (即: 当 Enumerator 的 MoveNext 方法被调用时)。

下面两种查询运算符会被立即执行,因为他们的返回值类型没有提供延迟执行的机制:

  • First、Count 之类的返回单个元素或者标量值的查询运算符。
  • ToArray、ToList、ToDictionary、ToLookup 之类的转换运算符。

除此之外的所有其他的运算符都是延迟执行的。

对于 LINQ 来说,延迟执行时非常重要的,因为它把查询的创建与查询的执行解耦了,这让我们可以向创建 SQL 查询那样,分成多个步骤来创建我们的 LINQ 查询。

原理

Note: 2017-04-19 Tony 发现 LINQ 的 延迟持行,其原理可能仅与 yield 有关。有待验证。

Resource

  1. LINQ 之路 6: 延迟执行 (Deferred Execution) - 网友评论: 其实只要你返回的是 IEnumerable<T>,也是延迟执行的 - IEnumerable<int> getX() { for(var i=0; i<10; i++) yield return i; }
  2. LINQ 之路 6: 延迟执行 (Deferred Execution) - 延迟执行的实现原理: 查询运算符通过返回装饰者 sequence (decorator sequence) 来支持延迟执行。

副作用

一、重复执行
延迟执行导致:当重复遍历查询结果时,查询会被重复执行。
如果不希望查询被重复执行,可以使用上述 ToList() 等转换运算符,获得查询结果,并将结果保存在临时变量中供后续使用,从而避免重复遍历查询。

二、变量捕获
延迟执行导致:如果查询的 lambda 表达式引用了程序的局部变量,查询会在执行时对变量进行捕获。这意味着,如果在查询定义之后改变了该变量的值,那么查询结果也会随之改变。
这里涉及 "闭包"。

博客园友们还发现 C# 4.0- 与 C# 5.0 中 foreach 的行为发生的变化,详见 LINQ 之路 6: 延迟执行 (Deferred Execution) - 网友评论

-- LINQ 之路 6: 延迟执行 (Deferred Execution)

Example - 使用 LINQ 操作 String

延迟执行

char x;
x = 'a';
var query =
	from c in "abcd"
	where c == x
	select c;
x = 'b';
Console.Write(string.Join(",", query));
x = 'd';
Console.Write(string.Join(",", query));
x = 'c';
Console.Write(string.Join(",", query));

执行结果 bdc

非延迟执行

string str = "adsf";
var query =
	from c in str
	select c;
str = "1234";
Console.WriteLine(string.Join(", ", query) == "a, d, s, f");

执行结果 True

Resource

  1. C# 中几种延迟执行 (Deferred Execution) 的探讨