linq是否比表面上的效率更高?


13

如果我写这样的话:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

是否与以下内容相同:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

还是在幕后有一些更像这样的魔术:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

还是完全不同?


4
您可以在ILSpy中查看。
2013年

1
ILSpy是您的朋友,这更像是第二个示例,而不是第一个但第二个ChaosPandion的答案。
迈克尔

Answers:


27

LINQ查询是惰性的。这意味着代码:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

做得很少。原始的enumerable(mythings)仅在使用结果所得的enumerable(things)时被枚举,例如通过foreach循环.ToList().ToArray()

如果调用things.ToList(),它将大致等同于后面的代码,枚举器可能会有一些(通常是微不足道的)开销。

同样,如果使用foreach循环:

foreach (var t in things)
    DoSomething(t);

其性能类似于:

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

惰性方法对可枚举对象的一些性能优势(与计算所有结果并将其存储在列表中相反)是它使用的内存很少(因为一次只存储一个结果),并且没有显着增加-前期费用。

如果可枚举仅被部分枚举,则这尤其重要。考虑以下代码:

things.First();

LINQ的实现方式mythings将仅枚举直至与where条件匹配的第一个元素。如果该元素在列表中处于较早位置,则可以极大地提高性能(例如,用O(1)代替O(n))。


1
LINQ和使用的等效代码之间的性能差异foreach是LINQ使用委托调用,这有一些开销。当条件执行得非常快时(这通常会这样做),这可能很重要。
2013年

2
这就是我的枚举器开销。在某些情况下,这可能是个问题,但是根据我的经验,这种情况并不常见-通常,开始所需的时间很少,或者与您正在执行的其他操作相比,其价值是不小的。
Cyanfish

Linq惰性评估的一个讨厌的限制是,除非通过诸如ToList或那样的方法,否则无法对枚举进行“快照” ToArray。如果正确地构建了这样的东西IEnumerable,就可以要求列表“快照”将来可能发生变化的任何方面,而不必生成所有内容。
超级猫

7

如下代码:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

等于什么都没有,因为懒惰的评估,什么也不会发生。

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

有所不同,因为将启动评估。

的每一项mythings将分配给第一个Where。如果通过,它将被赋予第二个Where。如果通过,它将成为输出的一部分。

所以看起来更像这样:

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}

7

除了延期执行(其他答案已经解释了,我将指出另一个细节),它更像您的第二个示例。

让我们试想一下,你叫ToListthings

Enumerable.Where返回的实现Enumerable.WhereListIterator。当您调用WhereWhereListIterator(又称为chaining Where-calls),您不再调用Enumerable.Where,而是Enumerable.WhereListIterator.Where实际将谓词组合在一起(使用Enumerable.CombinePredicates)。

所以它更像if(t.IsSomeValue && t.IsSomeOtherValue)


“返回一个Enumerable.WhereListIterator”使它对我单击。可能是一个非常简单的概念,但这就是我使用ILSpy忽略的概念。谢谢
ConditionRacer

如果您对更深入的分析感兴趣,请参阅Jon Skeet对这种优化的重新实现
2013年

1

不,它不一样。在您的示例中things是一个IEnumerable,此时它仍然只是一个迭代器,而不是实际的数组或列表。此外,由于things不使用循环,因此甚至不会评估循环。该类型IEnumerable允许迭代yield由Linq指令编排的元素,并使用更多指令进一步处理它们,这意味着最终您实际上只有一个循环。

但是,一旦添加了诸如.ToArray()或的指令,就将.ToList()命令创建实际的数据结构,从而对您的链进行限制。

看到这个相关的SO问题:https : //stackoverflow.com/questions/2789389/how-do-i-implement-ienumerable

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.