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

use*_*769 41 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
Run Code Online (Sandbox Code Playgroud)

3)延期执行是否还有其他好处?

Ree*_*sey 47

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

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

 var results = collection.Select(item => item.Foo).Where(foo => foo < 3).ToList();
Run Code Online (Sandbox Code Playgroud)

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

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

就个人而言,我会说你上面的第2项更多是副作用而不是利益 - 虽然它有时是有益的,但有时也会引起一些混乱,所以我只会认为这是"需要理解的东西"和不要说它是LINQ的好处.


为了回应您的编辑:

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

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

在我的示例中,返回类型是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);
}
Run Code Online (Sandbox Code Playgroud)

延迟执行导致序列本身只被枚举(foreach)一次,当它被使用时(by 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);
}
Run Code Online (Sandbox Code Playgroud)

  • @ user702769:我编辑过来向您展示差异 - 这有帮助吗? (2认同)
  • @ user702769:嗯,它有点不同,但是`IEnumerable <T>`只允许返回每个项目,一次一个.这意味着"ToLists"序列的枚举"拉过"值,并且每个运算符一次出现在一个值上.实际的枚举/单步只发生一次.这就是"延期执行"实际意味着什么. (2认同)
  • @ user702769:正如我所说的,我上面所做的只是概念性的 - 它实际上并没有将代码合并在一起(在LINQ对象中 - "IQueryable <T>`是不同的,而且有点不同) - 但是通过操作员一次一个,所以"收集"只能完全枚举一次. (2认同)

Dav*_*vy8 36

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

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;

    }
}
Run Code Online (Sandbox Code Playgroud)

(来源: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);
}
Run Code Online (Sandbox Code Playgroud)

打印:

1
1
3
5
13
21
55
89
233
377

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

  • @MateenUlhaq 抱歉回复晚了,但是“所有斐波那契数字”是什么意思?名单是无限的。如果你的意思是如果我知道我需要 10 个,为什么不提前计算它,因为有时你直到稍后才知道你需要多少。有时您不需要前 10 个,也许您需要进行分页并且您要求第 11-20 个值。也许您需要对其进行过滤以获取主要值。关键是您可以决定稍后在代码中如何过滤它,而该函数不需要知道它将如何过滤。 (3认同)

Nic*_*ris 6

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