给foreach语句剌上一小刀

      在C#中,foreach是个好东西,他可以迭代集合中的元素,并且可以无须知道集合的元素数量,使用起来要比for循环语句方便的多,这篇随笔记录的就是我对foreach语句的一些体会。

      对于那些技术高深的大牛们来说,一般在谈论某些技术时,总是说的面面俱到,而且十分严谨,就犹如庖丁解牛一般,而对于我来说解牛根本就谈不上(怕挨板砖),充其量就是给它剌上一小刀,至于能不能拉破皮就另一说了,这就是我为这篇随笔起名叫《给foreach语句剌上一小刀》的原因了,废话少说,进入正题了。

      假如有这样一个Int类型的数组:int[] intarray={1,2,3,4,5,6,7};

      我们用foreach语句输出intarray所有的元素,只需下面几行代码:

class Program
{
    static void Main(string[] args)
    {
        int[] intarray = { 1, 2, 3, 4, 5, 6, 7 };
        foreach (int item in intarray)
        {
            Console.WriteLine(item);
        }
        Console.ReadLine();
    }
}

运行结果

未命名

      首先看下foreach语句的声明方式,item 是一个迭代器,int 表示这个迭代器的类型,in 我认为是个标识符,intarray是要迭代的集合,简单描述下他们之间的关系,首先确定你要迭代那个集合,本例中需要迭代intarray数组,然后要清楚我们这个集合中的元素是什么类型的,本例中intarray是一个int类型的数组,所以他的元素是int类型的,如果无法确定集合中元素的类型,在FrameWork3.5中还可以使用隐式声明var来定义迭代器,这样编译器就会在编译的时候根据集合来判断迭代器item的类型了,然后我们定一个int类型的变量item作为迭代器,他的作用是代替intarray中的元素,至于 in的作用就是告诉迭代器item要迭代intarray,以上纯属个人理解,难免有理解错误的地方,欢迎高手们批评指正。

      隐式声明迭代器的代码如下:

       foreach (var item in intarray)
        {
            Console.WriteLine(item);
        }

      可以看出使用foreach语句遍历集合确实要比for方便,那么foreach语句在C#编译器中是如何被编译执行的呢?

      首先我们有必要研究一下Array数组,在C#中数组Array实现了IEnumerable、ICollection和IList接口,IEnumerable接口中声明了一个GetEnumerator()方法,他的作用是返回一个循环访问集合的枚举数,它的返回类型是IEnumerator接口类型,而这个接口类型中包含一个MoveNext()方法和Current只读属性,MoveNext()方法的作用是将枚举数推进到集合的下一个元素,其返回类型是bool类型,如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false,而Current属性是获取集合中的当前元素,它的返回类型是Object类型,注意它是只读的。

      在编译器中上面foreach语句会被解释成下面的代码:

      IEnumerator array = arr.GetEnumerator();
      while (array.MoveNext())
      {
          Console.WriteLine(array.Current);
      }
      Console.ReadLine();

      从上面的代码中可以看出foreach语句在编译过程中,实际上是调用被迭代的集合的GetEnumerator()方法返回一个Enumerator接口对象,然后构造一个While循环,在这个循环中以Enumerator的MoveNext()方法的返回值作为循环条件,用Enumerator的Current属性返回集合的每一个元素。

      通过上面的代码分析得到一个结论:在foreach语句中被迭代的对象不一定必须是集合,只要是该对象具有GetEnumerator()方法,并且该方法能够返回一个IEnumerator接口对象即可,到底是不是这样呢?还是做个实验吧。

我们定义一个Peoplo类,该类中定义一个GetEnumerator()方法,代码如下:

class Peoplo
{
    public IEnumerator GetEnumerator()
    {
        yield return "peoplo1";
        yield return "peoplo2";
        yield return "peoplo3";

    }
}

 

      GetEnumerator()方法返回了一个Enumerator接口对象(注意需要导入System.Collections命名空间),使用yield return语句来返回Enumerator集合。

      定义好Peoplo类后,我们就可以用foreach语句迭代了,代码如下:

Peoplo pl = new Peoplo();

foreach (var item in pl)
{
    Console.WriteLine(item);
}
Console.ReadLine();

      输出结果如下:

      未标题-1

      在foreach语句中pl对象它并不是一个可枚举的对象,他只是一个普通类的一个实例,可是它却被成功地迭代了,运行结果证明我们前面得到的结论是正确的。

      也就是说foreach语句中被迭代的对象可以是任何对象,只要是该对象具有GetEnumerator()方法,并且该方法能够返回一个IEnumerator接口对象即可,对于我们自己定义的类,我们必须手动创建一个GetEnumerator(),而如果这个类实现了IEnumerable接口、ICollection接口、IList接口中的任何一个或者是该类继承了实现以上三个接口中任何一个接口的类,那么就无须手动创建GetEnumerator()方法,因为以上三个接口中已经声明了该方法。

      总结:foreach语句为我们遍历集合提供了一个非常简便的手段,了解它在编译器中的运行机制,可以使我们更好的使用foreach语句,例如我以前一直认为foreach语句中被迭代的对象必须是一个可枚举的集合,现在我才知道原来我错了,原来只要这对象具有GetEnumerator()方法就可以了!

      朋友,如果你没有看见这篇随笔,你,知道吗???

posted on 2009-10-24 15:51  卜俊生  阅读(561)  评论(5编辑  收藏  举报