使用where条件vs Continue保护子句过滤foreach循环


24

我已经看到一些程序员使用此:

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

而不是我通常使用的位置:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

我什至看到了两者的结合。我真的很喜欢“继续”的可读性,尤其是在更复杂的条件下。性能上甚至有差异吗?通过数据库查询,我假设会有。常规列表呢?


3
对于常规列表,这听起来像微优化。
启示录

2
@zgnilec:...但实际上,这两个变体中的哪一个是优化版本?我对此当然有意见,但是仅仅看一下代码,并不是每个人都天生就清楚。
布朗

2
当然继续会更快。使用linq。在此处创建其他迭代器。
天启

1
@zgnilec-好的理论。小心发帖回答解释为什么你认为?当前存在的两个答案都相反。
Bobson

2
...因此,最重要的是:两种构造之间的性能差异可以忽略不计,并且两者的可读性和可调试性都可以实现。您偏爱的只是口味问题。
布朗

Answers:


63

我认为这是使用命令/查询分离的合适位置。例如:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

这也使您可以给查询结果起一个很好的自记录名称。它还可以帮助您发现重构的机会,因为重构仅查询数据或仅对数据进行变异的代码比尝试同时执行两者的混合代码要容易得多。

调试时,可以先中断一下,然后foreach快速检查validItems解析的内容是否符合您的期望。除非需要,否则您不必进入lambda。如果确实需要进入lambda,则建议将其分解为一个单独的函数,然后逐步执行。

性能上有区别吗?如果查询由数据库支持,则LINQ版本可能会运行得更快,因为SQL查询可能更有效。如果是LINQ to Objects,那么您将看不到任何实际的性能差异。与往常一样,分析您的代码并修复实际报告的瓶颈,而不是尝试预先预测优化。


1
为什么庞大的数据集会有所作为?仅仅因为lambda的微不足道的成本最终会加起来?
BlueRaja-Danny Pflughoeft 2015年

1
@ BlueRaja-DannyPflughoeft:是的,您是对的,此示例不涉及原始代码之外的其他算法复杂性。我删除了这句话。
Christian Hayter

这是否会导致对集合进行两次迭代?当然,考虑到其中只有有效项目,第二个项目要短一些,但是您仍然需要做两次,一次是过滤掉元素,第二次是使用有效项目。
安迪

1
@DavidPacker号。仅IEnumerableforeach循环驱动。
本杰明·霍奇森

2
@DavidPacker:这就是它的作用;大多数LINQ to Objects方法都是使用迭代器块实现的。上面的示例代码将仅对集合进行一次迭代,对Where每个元素执行一次lambda和循环主体(如果lambda返回true)。
Christian Hayter

7

当然,性能上会有差异,.Where()导致对每个项目都进行委托调用。但是,我完全不用担心性能:

  • 与其余遍历集合并检查条件的代码所用的时钟周期相比,调用委托所使用的时钟周期可以忽略不计。

  • 调用代表的性能损失大约是几个时钟周期,幸运的是,我们已经过了很长时间才不得不担心各个时钟周期的问题。

如果出于某种原因,性能在时钟周期级别上对您而言确实很重要,请使用List<Item>代替IList<Item>,以便编译器可以使用直接(且不可插入)调用代替虚拟调用,从而使的迭代器List<T>实际上是一个struct,不必装箱。但这确实是一件小事。

数据库查询是另一种情况,因为(至少在理论上)有可能将过滤器发送到RDBMS,从而大大提高了性能:只有匹配的行才能从RDBMS到您的程序。但是为此,我认为您必须使用linq,我不认为此表达式可以按原样发送到RDBMS。

您真的会if(x) continue;在需要调试该代码的那时候看到好处:单步执行if()s和continues可以很好地工作;单步进入过滤委托很痛苦。


那是当出现问题时,您想查看所有项目并在调试器中检查哪些项具有Field!= null,哪些项具有State!= null;使用foreach可能很难甚至不可能。
gnasher729 2015年

调试好点。进入Visual Studio中的问题并不是很糟糕,但是在调试时,如果不重新编译就不能重写lambda表达式,而使用时可以避免这种情况if(x) continue;
Paprik

严格来说,.Where仅被调用一次。什么被调用在每次迭代是滤波器委托(和MoveNextCurrent的枚举,当他们没有得到优化掉了)
CodesInChaos

@CodesInChaos让我花了一点时间去理解你在说什么,但是,当然,wh00ps是正确的,严格来说,.Where只被调用一次。固定它。
Mike Nakis 2015年
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.