LINQ + Foreach与Foreach + If的区别

57

我需要迭代一个对象列表,只对具有布尔属性设置为真的对象执行某些操作。 我正在考虑以下代码

foreach (RouteParameter parameter in parameters.Where(p => p.Condition))
{ //do something }

以及这段代码

foreach (RouteParameter parameter in parameters)
{ 
  if !parameter.Condition
    continue;
  //do something
}

第一段代码显然更加简洁,但我怀疑它会遍历两次列表——一次用于查询,一次用于循环。虽然这不是一个大型列表,所以我并不过于担心性能问题,但是遍历两次的想法让我感到有些恼火

问题:是否有一种优雅的方式可以在不遍历两次列表的情况下编写它?


7
事实证明,我误解了LINQ的延迟执行方式,而这些形式在执行上实际上是相同的。希望我能标记多个答案,因为下面所有这些答案都有所贡献。 - Joel
4个回答

132
Jon Skeet有时会进行实时的LINQ演示来解释它的工作原理。想象一下舞台上有三个人。在左边,我们有一个持有洗过牌的牌组的人。中间的那个人只传递红色的牌,右边的那个人想要牌。
右边的那个人戳了中间的那个人。中间的那个人戳了左边的那个人。左边的那个人递给中间的那个人一张牌。如果这是黑色的,中间的那个人会把它扔在地上并继续戳,直到他得到一张红色的牌,然后将其递给右边的那个人。然后右边的那个人再次戳中间的那个人。
此过程将持续到左边的那个人用完所有的牌为止。 这副牌仅被从头到尾循环了一次。然而,左边的人和中间的人都处理了52张牌,右边的人处理了26张牌。共对卡片进行了52 + 52 + 26次操作,但是该牌组仅被循环遍历了一次
你的“LINQ”版本和“continue”版本是相同的东西;如果你有
foreach(var card in deck)
{
    if (card.IsBlack) continue;
    ... use card ...

然后就有52个操作从牌堆中取出每张卡牌,还有52个操作测试每张卡牌是否为黑色,以及26个操作对红色卡牌进行操作。完全一样的事情。


应该指出,这个列表不会被枚举两次并不是因为魔法。延迟执行在其他答案中已经提到,因此请参考那些答案来解释为什么它能像@Eric所描述的那样工作。 - hemp
我非常喜欢你解释这个的方式。 - Tarik

37
大多数Linq运算符,如Where,都实现了支持延迟和惰性执行。在您的示例中,列表仅被迭代一次,因为IEnumerable返回的枚举器将枚举列表,直到找到与谓词匹配的项,产生它并仅在请求下一个元素时继续。

从代码角度来看,我更喜欢使用where变体,尽管可能会有人认为您可以为parameters.Where(p => p.Condition)声明一个局部变量。
Jon Skeet的Edulinq系列深受推荐,阅读其中的一些内容应该有助于您理解LINQ运算符。

27

实际上,并不是“循环两次”。.Where子句使用延迟执行。换句话说,当您调用.Where时,实际上几乎没有任何工作被执行,但是当您遍历结果时,它将在原始列表上进行迭代,并仅传递符合条件的项。如果您从代码执行的角度来考虑它,您实际上正在执行以下操作:

Func<Parameter, bool> matchesCondition = p => p.Condition;
foreach(var parameter in parameters)
{
    if(matchesCondition(parameter))
    {
        ...
    }
}

就样式而言,我个人更喜欢类似于:

var matchingParameters = parameters.Where(p => p.Condition);
foreach(var parameter in matchingParameters)
{
}

-3

我更喜欢这个:

theList.Where(itm => itm.Condition).ToList().ForEach(itmFE => { itmFe.DoSomething(); }); 

11
这实际上是将列表枚举两次,这正是提问者不希望看到的。 - Nick Babcock
我想知道你为什么要这样做。上面给出的答案很出色。 - Aakash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接