辅导:C语言基础C#之11:PreferforeachLoops
来源:优易学  2011-10-13 10:44:31   【优易学:中国教育考试门户网】   资料下载   IT书店
  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());
  对于当前的和未来的c#编译器(版本1.1或者更高),Loop 1是最好的,它甚至是打字最少的,你个人的生产力能够提高。(C#1.0的编译器为Loop1生成比较慢的代码,因此在那个版本里面Loop2是最好的)。Loop 3 ,这个多数C和C++程序员会认为最高效的结构,是最坏的选择。通过将长度变量提升到循环之外,你做了如下的改变:妨碍了JIT编译器移除在循环内部检测范围的机会。
  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();
  }
  JITC#编译器就是不喜欢你试图这样帮助它。你将对长度这个属性的访问提升到循环外部来的尝试,只会使JIT编译器做更多的工作甚至生成更慢的代码。CLR的一个保障就是你不能编写超出你的变量所拥有的内存的代码。运行时在访问每个特定的数组元素之前,会为实际的数组边界生成一个测试(不是你的len变量)。你以2遍的代价得到了一次边界的检测。
  Examda提示: 需要在循环的每次重复时,为数组的索引检查付出代价,并且做了2次。Loop 1 和Loop 2更快的原因是C#编译器和JIT编译器能够验证循环的边界,保证安全。任何时候,当循环变量不是数组长度时,边界检查会在每次重复时执行。
  在原来的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());
  }
  相反,foreach表达式为数组生成了这样的结构:
  for (int index = 0;index < foo.Length;index++)
  Console.WriteLine(foo[index].ToString());
  foreach总是生成最好的代码。你没必要去记住哪个构造生成了最高效的循环结构:foreach和编译器会为你做这个工作。
  如果效率对你来说还不够的话,那么考虑语言的互操作性。这个世界上的一些家伙(是的,他们中的多数使用其他的编程语言)强烈的相信索引变量以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 });
  这个代码足以使任何人畏缩从而仅仅编写以0开始的数组了。但是有的人非常顽固。尽你所能的努力,他们还会从1开始数。幸运的是,这是你可以强加给编译器的很多问题之一。使用foreach来迭代test数组:
  foreach (int j in test)
  Console.WriteLine(j);
  Foreach表达式知道如何来检查数组的上限和下限,因此你没必要——同时这也和手工编码一样快,而不用管一些人决定用什么不同的下限。
  foreach为你加入了其他语言的好处。循环变量是只读的:使用foreach时,你不能替换一个集合中的对象。同时,具有向正确类型的直接转换。如果集合中包含有错误类型的对象,迭代就会抛出异常。
  Examda提示: 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( );
  相反,你可以这样来简化绘制棋盘:
  foreach( Square sq in theBoard )
  sq.PaintSquare( );
  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( );
  事实上,在一个多维数组上,即使每一维有不同的下限,foreach还是能够工作的。我不想写下那种代码,甚至是一个例子。但是当其他人写下那样的集合代码时,foreach能够对付。
  如果你以后发现需要修改来自数组的下级的数据结构,foreach也赋予你保持很多代码完整的弹性。我们以一个简单的数组来进行讨论:
  int [] foo = new int[100];
  假设,在以后的某个时候,你意识到需要不是那么容易被数组类处理的能力。你可以很简单的将数组修改成ArrayList:
  // Set the initial size:
  ArrayList foo = new ArrayList( 100 );
  同时,任何对于循环的手工编码都会被破坏:
  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];
  然而,foreach循环编译成不同的代码:它自动将每个操作数转换成适合的类型。不需要你做任何转换。转换不仅仅是为了对集合类进行标准化,或者是为了任何集合类型都可以与foreach一起使用。
  如果你支持.Net环境下对集合的规则,你的类型的用户可以使用foreach来对所有的成员进行迭代。对于foreach表达式,将它考虑成一个集合类型,一个必须拥有至少一个属性的类。公公的GetEnumerator()方法的存在形成了一个集合类。直接实现IEnumerable接口创建一个集合类型。实现IEnumerator接口创建一个集合类型。Foreach和它们中的任何一个都可以一起使用。
  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;
  }
  }
  如果编译器能判定这个enumerator是否实现了IDisposable,它自动优化处于最终括号中的代码。但是对于你来说,看到这一点非常重要,无论怎么回事,foreach都生成正确的代码。
  foreach是一个很通用的表达式。它为数组的上限和下限生成正确的代码,对多维数组进行迭代,将操作数强制转换成正确的类型(使用最有效的构造),而且,最主要的是,生成最高效的循环结构。它是对集合进行迭代的最好方法。有了它,你可以创建更持久的代码,在首先出现的地方进行编写也是容易的。使用它,是一个小小的生产力的提升,但是随着时间的变化,它的效果会增加的。

责任编辑:小草

文章搜索:
 相关文章
热点资讯
资讯快报
热门课程培训