Item 11: Prefer foreach Loops
优先选择foreach循环
The C# foreach statement is more than just a variation of the do, while, or for loops. It generates the best iteration code for any collection you have. Its definition is tied to the collection interfaces in the .NET Framework, and the C# compiler generates the best code for the particular type of collection. When you iterate collections, use foreach instead of other looping constructs. Examine these three loops:
C#的foreach表达式不仅仅是do、while、for循环的变体。它为你拥有的任何集合生成最好的迭代代码。它的定义和.Net框架中的集合接口相绑定,C#编译器为这个特定类型的集合生成最优的代码。当你迭代集合的时候,使用foreach代替其它循环结构。看这些循环:
- int[] foo = new int[100];
- // Loop 1:
- foreach (int i in foo)
- Console.WriteLine(i.ToString());
- // Loop 2:
- for (int index = 0;index < foo.Length;index++)
- Console.WriteLine(foo[index].ToString());
- // Loop 3:
- int len = foo.Length;
- for (int index = 0;index < len;index++)
- Console.WriteLine(foo[index].ToString());
For the current and future C# compilers (version 1.1 and up), loop 1 is best. It's even less typing, so your personal productivity goes up. (The C# 1.0 compiler produced much slower code for loop 1, so loop 2 is best in that version.) Loop 3, the construct most C and C++ programmers would view as most efficient, is the worst option. By hoisting the Length variable out of the loop, you make a change that hinders the JIT compiler's chance to remove range checking inside the loop.
对于当前的和未来的c#编译器(版本1.1或者更高),Loop 1是最好的,它甚至是打字最少的,你个人的生产力能够提高。(C#1.0的编译器为Loop1生成比较慢的代码,因此在那个版本里面Loop2是最好的)。Loop 3 ,这个多数C和C++程序员会认为最高效的结构,是最坏的选择。通过将长度变量提升到循环之外,你做了如下的改变:妨碍了JIT编译器移除在循环内部检测范围的机会。
C# code runs in a safe, managed environment. Every memory location is checked, including array indexes. Taking a few liberties, the actual code for loop 3 is something like this:
C#代码运行在安全的、托管环境上。每次内存分配都被检查,包括数组的索引。通过使用一些库,Loop 3实际的代码有点像下面这样:
- // Loop 3, as generated by compiler:
- int len = foo.Length;
- for (int index = 0;index < len;index++)
- {
- if (index < foo.Length)
- Console.WriteLine(foo[index].ToString());
- else
- throw new IndexOutOfRangeException();
- }
The JIT C# compiler just doesn't like you trying to help it this way. Your attempt to hoist the Length property access out of the loop just made the JIT compiler do more work to generate even slower code. One of the CLR guarantees is that you cannot write code that overruns the memory that your variables own. The runtime generates a test of the actual array bounds (not your len variable) before accessing each particular array element. You get one bounds check for the price of two.
JITC#编译器就是不喜欢你试图这样帮助它。你将对长度这个属性的访问提升到循环外部来的尝试,只会使JIT编译器做更多的工作甚至生成更慢的代码。CLR的一个保障就是你不能编写超出你的变量所拥有的内存的代码。运行时在访问每个特定的数组元素之前,会为实际的数组边界生成一个测试(不是你的len变量)。你以2遍的代价得到了一次边界的检测。
You still pay to check the array index on every iteration of the loop, and you do so twice. The reason loops 1 and 2 are faster is that the C# compiler and the JIT compiler can verify that the bounds of the loop are guaranteed to be safe. Anytime the loop variable is not the length of the array, the bounds check is performed on each iteration.
你仍然需要在循环的每次重复时,为数组的索引检查付出代价,并且做了2次。Loop 1 和Loop 2更快的原因是C#编译器和JIT编译器能够验证循环的边界,保证安全。任何时候,当循环变量不是数组长度时,边界检查会在每次重复时执行。
The reason that foreach and arrays generated very slow code in the original C# compiler concerns boxing, which is covered extensively in Item 17. Arrays are type safe. foreach now generates different IL for arrays than other collections. The array version does not use the IEnumerator interface, which would require boxing and unboxing:
在原来的C#编译器下,foreach和数组生成非常慢代码的原因在于装箱,这会在Item 17中覆盖。数组是类型安全的。现在foreach为数组生成了与其他集合不同的IL。数组的版本没有使用IEnumerator接口,该接口要求装箱和拆箱操作:
- IEnumerator it = foo.GetEnumerator();
- while (it.MoveNext())
- {
- int i = (int)it.Current; // box and unbox here.
- Console.WriteLine(i.ToString());
- }
Instead, the foreach statement generates this construct for arrays:
相反,foreach表达式为数组生成了这样的结构:
- for (int index = 0;index < foo.Length;index++)
- Console.WriteLine(foo[index].ToString());
foreach always generates the best code. You don't need to remember which construct generates the most efficient looping construct: foreach and the compiler will do it for you.
foreach总是生成最好的代码。你没必要去记住哪个构造生成了最高效的循环结构:foreach和编译器会为你做这个工作。
If efficiency isn't enough for you, consider language interop. Some folks in the world (yes, most of them use other programming languages) strongly believe that index variables start at 1, not 0. No matter how much we try, we won't break them of this habit. The .NET team tried. You have to write this kind of initialization in C# to get an array that starts at something other than 0:
如果效率对你来说还不够的话,那么考虑语言的互操作性。这个世界上的一些家伙(是的,他们中的多数使用其他的编程语言)强烈的相信索引变量以1开始,而不是0.无论我们如何努力,都不能打破他们的这个习惯。.Net小组就曾经尝试过。你不得不在C#中写下这样的初始化,以便获得一个以其他非0开始的数组。
- // Create a single dimension array.
- // Its range is [ 1 .. 5 ]
- Array test = Array.CreateInstance(typeof(int), new int[] { 5 }, new int[] { 1 });
This code should be enough to make anybody cringe and just write arrays that start at 0. But some people are stubborn. Try as you might, they will start counting at 1. Luckily, this is one of those problems that you can foist off on the compiler. Iterate the test array using foreach:
这个代码足以使任何人畏缩从而仅仅编写以0开始的数组了。但是有的人非常顽固。尽你所能的努力,他们还会从1开始数。幸运的是,这是你可以强加给编译器的很多问题之一。使用foreach来迭代test数组:
- foreach (int j in test)
- Console.WriteLine(j);
The foreach statement knows how to check the upper and lower bounds on the array, so you don't have toand it's just as fast as a hand-coded for loop, no matter what different lower bound someone decides to use.
Foreach表达式知道如何来检查数组的上限和下限,因此你没必要——同时这也和手工编码一样快,而不用管一些人决定用什么不同的下限。
foreach adds other language benefits for you. The loop variable is read-only: You can't replace the objects in a collection using foreach. Also, there is explicit casting to the correct type. If the collection contains the wrong type of objects, the iteration throws an exception.
foreach为你加入了其他语言的好处。循环变量是只读的:使用foreach时,你不能替换一个集合中的对象。同时,具有向正确类型的直接转换。如果集合中包含有错误类型的对象,迭代就会抛出异常。
foreach gives you similar benefits for multidimensional arrays. Suppose that you are creating a chess board. You would write these two fragments:
foreach对于多维数组也提供了相似的好处。假设你正在创建一个棋盘。你可能写下这样的2个片段:
- private Square[,] theBoard = new Square[ 8, 8 ];
- // elsewhere in code:
- for ( int i = 0; i < theBoard.GetLength(0); i++ )
- for( int j = 0; j < theBoard.GetLength(1); j++ )
- theBoard[ i, j ].PaintSquare( );
Instead, you can simplify painting the board this way:
相反,你可以这样来简化绘制棋盘:
- foreach( Square sq in theBoard )
- sq.PaintSquare( );
The foreach statement generates the proper code to iterate across all dimensions in the array. If you make a 3D chessboard in the future, the foreach loop just works. The other loop needs modification:
foreach表达式生成合适的代码在该数组的所有维度上进行迭代。如果你将来会制作一个3D的棋盘,foreach还是能工作。其它的循环就需要进行修改了:
- for ( int i = 0; i < theBoard.GetLength( 0 ); i++ )
- for( int j = 0; j < theBoard.GetLength( 1 ); j++ )
- for( int k = 0; k < theBoard.GetLength( 2 ); k++ )
- theBoard[ i, j, k ].PaintSquare( );
In fact, the foreach loop would work on a multidimensional array that had different lower bounds in each direction. I don't want to write that kind of code, even as an example. But when someone else codes that kind of collection, foreach can handle it.
事实上,在一个多维数组上,即使每一维有不同的下限,foreach还是能够工作的。我不想写下那种代码,甚至是一个例子。但是当其他人写下那样的集合代码时,foreach能够对付。
foreach also gives you the flexibility to keep much of the code intact if you find later that you need to change the underlying data structure from an array. We started this discussion with a simple array:
如果你以后发现需要修改来自数组的下级的数据结构,foreach也赋予你保持很多代码完整的弹性。我们以一个简单的数组来进行讨论:
- int [] foo = new int[100];
Suppose that, at some later point, you realize that you need capabilities that are not easily handled by the array class. You can simply change the array to an ArrayList:
假设,在以后的某个时候,你意识到需要不是那么容易被数组类处理的能力。你可以很简单的将数组修改成ArrayList:
- // Set the initial size:
- ArrayList foo = new ArrayList( 100 );
Any hand-coded for loops are broken:
同时,任何对于循环的手工编码都会被破坏:
- int sum = 0;
- // won't compile: ArrayList uses Count, not Length
- for (int index = 0;index < foo.Length;index++)
- // won't compile: foo[ index ] is object, not int.
- sum += foo[index];
However, the foreach loop compiles to different code that automatically casts each operand to the proper type. No changes are needed. It's not just changing to standard collections classes, eitherany collection type can be used with foreach.
然而,foreach循环编译成不同的代码:它自动将每个操作数转换成适合的类型。不需要你做任何转换。转换不仅仅是为了对集合类进行标准化,或者是为了任何集合类型都可以与foreach一起使用。
Users of your types can use foreach to iterate across members if you support the .NET environment's rules for a collection. For the foreach statement to consider it a collection type, a class must have one of a number of properties. The presence of a public GetEnumerator() method makes a collection class. Explicitly implementing the IEnumerable interface creates a collection type. Implementing the IEnumerator interface creates a collection type. foreach works with any of them.
如果你支持.Net环境下对集合的规则,你的类型的用户可以使用foreach来对所有的成员进行迭代。对于foreach表达式,将它考虑成一个集合类型,一个必须拥有至少一个属性的类。公公的GetEnumerator()方法的存在形成了一个集合类。直接实现IEnumerable接口创建一个集合类型。实现IEnumerator接口创建一个集合类型。Foreach和它们中的任何一个都可以一起使用。
foreach has one added benefit regarding resource management. The IEnumerable interface contains one method: GetEnumerator(). The foreach statement on an enumerable type generates the following, with some optimizations:
foreach有一个附加的好处,就是忽略了资源管理。IEnumerable接口包含了一个方法:GetEnumerator()。用在一个enumerable 类型上的foreach表达式生成下列的代码,同时,它进行了一些优化:
- IEnumerator it = foo.GetEnumerator() as IEnumerator;
- using (IDisposable disp = it as IDisposable)
- {
- while (it.MoveNext())
- {
- int elem = (int)it.Current;
- sum += elem;
- }
- }
The compiler automatically optimizes the code in the finally clause if it can determine for certain whether the enumerator implements IDisposable. But for you, it's more important to see that, no matter what, foreach generates correct code.
如果编译器能判定这个enumerator是否实现了IDisposable,它自动优化处于最终括号中的代码。但是对于你来说,看到这一点非常重要,无论怎么回事,foreach都生成正确的代码。
foreach is a very versatile statement. It generates the right code for upper and lower bounds in arrays, iterates multidimensional arrays, coerces the operands into the proper type (using the most efficient construct), and, on top of that, generates the most efficient looping constructs. It's the best way to iterate collections. With it, you'll create code that is more likely to last, and it's simpler for you to write in the first place. It's a small productivity improvement, but it adds up over time.
foreach是一个很通用的表达式。它为数组的上限和下限生成正确的代码,对多维数组进行迭代,将操作数强制转换成正确的类型(使用最有效的构造),而且,最主要的是,生成最高效的循环结构。它是对集合进行迭代的最好方法。有了它,你可以创建更持久的代码,在首先出现的地方进行编写也是容易的。使用它,是一个小小的生产力的提升,但是随着时间的变化,它的效果会增加的。
本文探讨了C#中的foreach循环的优点,包括其在不同场景下的高效性和灵活性。foreach不仅为数组和其他集合提供最佳迭代代码,还能自动处理类型转换和边界检查,减少编程错误。此外,它还支持语言间的互操作性,并能在数据结构变化时保持代码稳定。
2554

被折叠的 条评论
为什么被折叠?



