在LINQ中推迟执行有什么好处?
LINQ使用延迟执行模型,这意味着在调用Linq运算符时不会返回结果序列,而是这些运算符返回一个对象,只有当我们枚举这个对象时才产生一个序列的元素。
虽然我明白延迟查询是如何工作的,但我在理解延迟执行的好处方面遇到了一些麻烦:
1)我读过延迟查询执行只有当你真的需要的结果可以是很大的好处。 那么这有什么好处?
2)延迟查询的其他优点是,如果您定义了一次查询,那么每次枚举结果时,如果数据发生更改,您将得到不同的结果。
a)但是从下面的代码可以看出,即使不使用延迟查询,我们也可以达到相同的效果(因此,每次枚举资源时,如果数据发生更改,我们都会得到不同的结果):
List<string> sList = new List<string>( new[]{ "A","B" }); foreach (string item in sList) Console.WriteLine(item); // Q1 outputs AB sList.Add("C"); foreach (string item in sList) Console.WriteLine(item); // Q2 outputs ABC
3)延期执行还有其他好处吗?
主要的好处是这使得过滤操作(LINQ的核心)更有效率。 (这实际上是您的项目#1)。
例如,像这样的LINQ查询:
var results = collection.Select(item => item.Foo).Where(foo => foo < 3).ToList();
通过延迟执行,上面的迭代会迭代你的集合,并且每次在迭代期间请求一个项目,执行映射操作,过滤,然后使用结果构build列表。
如果你每次都要使LINQ完全执行,每个操作( Select
/ Where
)都必须遍历整个序列。 这会使链式操作非常低效。
就个人而言,我想说上面的第二项是更多的一个副作用,而不是一个好处 – 虽然有时候是有益的,但也有时会造成一些混乱,所以我只会考虑这个“要理解的东西”和不要把它作为LINQ的一个好处。
为了回应你的编辑:
在你的特定例子中,在这两种情况下,Select将迭代集合并返回一个types为item.Foo的IEnumerable I1。 Where()然后枚举I1并返回types为item.Foo的IEnumerable <> I2。 I2然后将被转换为列表。
这是不正确的 – 推迟执行可以防止发生这种情况。
在我的例子中,返回types是IEnumerable<T>
,这意味着它是一个可枚举的集合,但由于延迟执行,实际上并没有枚举。
当您调用ToList()
,整个集合被枚举。 结果最终在概念上看起来更像(虽然当然不同):
List<Foo> results = new List<Foo>(); foreach(var item in collection) { // "Select" does a mapping var foo = item.Foo; // "Where" filters if (!(foo < 3)) continue; // "ToList" builds results results.Add(foo); }
延迟执行导致序列本身只能被枚举(foreach) 一次 ,当它被使用时(由ToList()
)。 没有延期执行,看起来更像(概念上):
// Select List<Foo> foos = new List<Foo>(); foreach(var item in collection) { foos.Add(item.Foo); } // Where List<Foo> foosFiltered = new List<Foo>(); foreach(var foo in foos) { if (foo < 3) foosFiltered.Add(foo); } List<Foo> results = new List<Foo>(); foreach(var item in foosFiltered) { results.Add(item); }
推迟执行的另一个好处是它可以让你使用无限的系列。 例如:
public static IEnumerable<ulong> FibonacciNumbers() { yield return 0; yield return 1; ulong previous = 0, current = 1; while (true) { ulong next = checked(previous + current); yield return next; previous = current; current = next; } }
(来源: http : //chrisfulstow.com/fibonacci-numbers-iterator-with-csharp-yield-statements/ )
然后您可以执行以下操作:
var firstTenOddFibNumbers = FibonacciNumbers().Where(n=>n%2 == 1).Take(10); foreach (var num in firstTenOddFibNumbers) { Console.WriteLine(num); }
打印:
1
1
3
五
13
21
55
89
233
377
如果没有延迟执行,你会得到一个OverflowException
或者如果操作没有checked
它会无限运行,因为它绕了ToList
(如果你在它上面调用ToList
会最终导致OutOfMemoryException
)
延迟执行的一个重要好处是您可以收到最新的数据。 这可能会影响性能(特别是在处理荒谬的大型数据集时),但同样,在原始查询返回结果时数据可能已经发生了变化。 延迟执行可确保在数据库更新快速的情况下,您将从数据库中获取最新信息。