在 C# 中是不是每次都评估循环声明?

Posted

技术标签:

【中文标题】在 C# 中是不是每次都评估循环声明?【英文标题】:Within C# is a loop declaration evaluated each time?在 C# 中是否每次都评估循环声明? 【发布时间】:2014-06-15 14:55:30 【问题描述】:

这是一个非常挑剔的问题,但好奇心战胜了我。

每次执行循环时,for 循环是否都会重新评估 RHS 条件?见下文。

for(int i = 0; i < collection.Count; i++)


在整个代码库中执行以下操作是否是一种好习惯?

for(int i = 0, n = collections.Count; i < n; i++)


或者编译器是否进行了这些优化/它们可以忽略不计(即使代码库很大)?

【问题讨论】:

第二个循环无法编译。你错过了; i &lt; n;吗? 为什么不改用foreach?这样优化就不用担心了。 @FrédéricHamidi 因为你可能不想为每个元素做点什么? @FrédéricHamidi 因为也许你想对索引做点什么 @MarcinJuraszek 感谢您发现 :) 【参考方案1】:

条件在每次迭代之前重新计算(除非编译器检测到条件是常量),并且可以编写依赖于此的代码(例如调用返回值的函数每次都不一样)。因此,如果您有一个评估成本很高的条件,并且您确信它会保持不变,那么在循环之前对其进行一次评估确实是一个好主意。然而,对于大多数标准集合类型来说,Count 是一个如此快速的操作,以至于微不足道的性能提升不值得降低可读性。

【讨论】:

未指定collection 的类型。如果它被声明为IEnumerable&lt;T&gt; 并持有对ICollection&lt;T&gt; or ICollection, Count 实现的引用,将会有点慢;如果它被声明为 IEnumerable&lt;T&gt; 并持有对未实现这些特定泛型类型的任何东西的引用,即使它实现了 IList&lt;U&gt; 用于派生自 T 的某些 UCount 可能会非常慢. @supercat:是的;我对此进行了澄清。 @supercat 如果是IEnumerable&lt;T&gt;,那么它将是Count(),而不是Count。虽然可能有一个非常慢的属性,但这将是非常不寻常的;很可能您需要有一个自定义集合类型,它只是做了一些非常愚蠢的事情,让它慢到足以成为问题。 @Servy:我错过了这个区别。即使有这种区别,类型实现ICollection&lt;T&gt; 的事实并不意味着Count 将与读取变量一样快。对于许多类型,它倾向于至少需要一个虚拟方法调度。此外,如果一个集合在枚举期间可能附加了项目,代码应该清楚循环是否应该包含这些项目。 @supercat 是的,正如我所说,自定义集合可能会在 Count 方法中做各种邪恶的事情,这可能很慢; BCL 集合类型不会这样做。尝试删除虚拟调度几乎可以肯定是不值得做的微优化,并且在枚举集合时对其进行变异是一个非常的坏主意;应该不惜一切代价避免的事情。【参考方案2】:

如果条件的右侧是恒定的(因此它不会在循环内部发生变化),我相信优化器会将 collections.Count 移到循环顶部之外。如果集合在循环中被修改,它将每次都进行评估。

如果您使用了 foreach,您会注意到您无法更改循环内的枚举器,因为 foreach 使用这种优化来加快循环......

您总是可以编写一个带有几个循环的小型演示应用程序,一个在循环内修改,一个不修改,然后在其上运行 ildasm 并查看生成的代码。

【讨论】:

以上是关于在 C# 中是不是每次都评估循环声明?的主要内容,如果未能解决你的问题,请参考以下文章

c# 在 Lambda 表达式中声明变量

如何在循环内声明一个新变量而不是错误?

for 循环是不是在每次迭代中重新评估其主体中的函数?

循环中的let和const声明

c#如何在项目中声明一个静态的List对象?(给出思路即可)

在每次迭代中重新声明变量是不是比在每次迭代后重置它们更快?