这是 C# 中最常被误解的循环行为之一。
您需要了解以下内容:
循环边界计算,如果
非常量且涉及变量,
属性访问、函数调用或委托调用
将在每个之前重新计算边界值
循环的迭代。
所以,例如:
for( int i = 0; i < 1234*1234; i++ ) { ... }
在这种情况下,表达式1234*1234 是一个编译时间常数,因此不会在每次迭代时重新计算。其实是在编译时计算出来的,换成一个常数。
但是,在这种情况下:
int k = 10;
for( int i = 0; i < k; i++ ) { k -= 1; ... }
k 的值必须在每次迭代时检查。 毕竟它可以改变 .. 在这个例子中确实如此。幸运的是,由于k 只是一个局部变量,因此访问它的成本非常低 - 在许多情况下,它要么保留在本地 CPU 缓存中,要么甚至保存在寄存器中(取决于 JIT 如何处理和发出机器代码)。
如果是这样的情况:
IEnumerable<int> sequence = ...;
for( int i = 0; i < sequence.Count(); i++ ) { ... }
计算sequence.Count() 的成本可能相当昂贵。而且由于它是在循环的每次迭代中进行评估的,因此可以快速累加。
编译器不能优化循环边界表达式中出现的方法或属性的调用,因为它们也可能随着每次迭代而改变。想象一下,如果上面的循环写成:
IEnumerable<int> sequence = ...;
for( int i = 0; i < sequence.Count(); i++ ) {
sequence = sequence.Concat( anotherItem );
}
显然sequence 在每次迭代中都会发生变化……因此Count() 可能在每次迭代中都不同。编译器不会尝试执行一些静态分析来确定循环边界表达式是否可能是常量......如果不是不可能的话,这将是极其复杂的。相反,它假定如果表达式不是常量,则必须在每次迭代时对其进行计算。
现在,在大多数情况下,计算循环边界约束的成本会相对便宜,因此您不必担心。但是您确实需要了解编译器如何处理这样的循环边界。此外,作为开发人员您需要小心使用具有副作用的属性或方法作为边界表达式的一部分 - 毕竟,这些副作用会在循环的每次迭代中发生。