LINQ中的延迟执行有什么好处?

时间:2011-09-06 17:51:05

标签: 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)延期执行还有其他好处吗?

3 个答案:

答案 0 :(得分:46)

主要的好处是,这允许过滤操作(LINQ的核心)更加高效。 (这实际上是你的第1项)。

例如,采用这样的LINQ查询:

 var results = collection.Select(item => item.Foo).Where(foo => foo < 3).ToList();

使用延迟执行,上面会迭代您的集合一次,并且每次在迭代期间请求项目时,执行映射操作,过滤器,然后使用结果来构建列表。 / p>

如果每次都要使LINQ完全执行,则每个操作(Select / Where)都必须遍历整个序列。这会使链式操作非常低效。

就我个人而言,我说上面的第2项更多的是副作用而不是利益 - 虽然它有时也是有益的,但有时会引起一些混乱,所以我只会考虑这个“理解“而不是将其视为LINQ的好处。


回复您的修改:

  

在您的特定示例中,在两种情况下,Select将迭代集合并返回类型为item.Foo的IEnumerable I1。然后Where()将枚举I1并返回IEnumerable&lt;&gt; I2类型item.Foo。然后I2将转换为List。

这不是真的 - 延迟执行会阻止这种情况发生。

在我的示例中,返回类型为IEnumerable<T>,这意味着它是可以枚举的集合,但由于延迟执行,它实际上并未枚举。 / p>

当您致电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);
}

答案 1 :(得分:35)

延迟执行的另一个好处是它允许您使用无限系列。例如:

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
5
13个
21个
55个
89个
233个
377

如果没有延迟执行,你会得到一个OverflowException,或者如果操作不是checked,它将无限运行,因为它包裹着(如果你在其上调用ToList会导致最终OutOfMemoryException

答案 2 :(得分:6)

延迟执行的一个重要好处是您可以获得最新数据。这可能会影响性能(特别是如果您处理的是荒谬的大型数据集),但同样地,在原始查询返回结果时,数据可能已经发生了变化。延迟执行确保您可以在数据库快速更新的情况下从数据库中获取最新信息。