黑马程序猿——C#枚举器深入解析

来源:互联网 发布:iscroll.js 下载 编辑:程序博客网 时间:2024/06/05 04:35

 -------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!  -------

 废话不说了,上码,如果你可以一眼看穿下面这段代码的执行流程,请您就飘过吧(这段代码摘抄自《C#高级编程》,第七版,让大家带着问题来学习我觉得是很好的方式)

 

static void Main()        

{            

      var game = new GameMoves();

                   var a = game.Cross();            

      while (a.MoveNext())

        {

        a = a.Current as IEnumerator;

      }            

      Console.Read();        

}

public class GameMoves    

{        

    IEnumerator cross;        

    IEnumerator circle;        

    public GameMoves()        

    {            

        cross = Cross();            

        circle = Circle();        

    }        

     int move = 0;        

    public const int MaxMoves = 10;        

    public IEnumerator Cross()        

    {            

        while (true)            

        {                

             Console.WriteLine("Cross,move{0}", move);                

            if (++move >= MaxMoves) yield break;                

            yield return circle;                    

        }        

     }        

    public IEnumerator Circle()        

    {            

        while (true)            

        {                

            Console.WriteLine("Circle,move{0}", move);                

             if (++move >= MaxMoves) yield break;                

            yield return cross;            

        }        

    }    

}    

上面的代码我不会做太多的解释,如果你理解了本文内容,你应该可以看懂(注:实在看不懂,就用单步执行吧)。下面再来看看另外这段代码,当我第一次看到这段代码的执行结果时,被惊呆了,完全颠覆了我两年编程学习对函数执行的认识。

class Program    

{        

     static void Main(string[] args)        

    {               

        var ie = IE();            

         Console.WriteLine("IE()已经调用了,你看到输出了吗?");                     ie.MoveNext();            

        Console.Read();         

     }        

    public static IEnumerator IE()        

    {             

        while (true)             

        {                 

            Console.WriteLine("你在调用IE()时看不到我的执行,我不是并行方法啊哦\n哈哈,神奇吧,除非你用IE()的返回值调用了MoveNext()\n并且每次调用都可以看到我哦");                  

             yield return "任意类型都可以直接返回哦";             

        }         

     }          

}

这段代码稍后我会详细的解释,为了解释清楚,请耐心听我慢慢道来,再看一段代码(哈哈,学编程的,要习惯哦)    

static void Main(string[] args)        

{            

    String[] myArr = new String[] { "The",  "quick", "brown"};                           

              Console.WriteLine("用标准的C#语法使用枚举器");            

    foreach (var str in myArr)

              {  

                   Console.WriteLine(str);            

    }            

    Console.WriteLine("使用枚举接口使用枚举器");

              var myEnumerator = myArr.GetEnumerator();

              while ((myEnumerator.MoveNext()) && (myEnumerator.Current != null))

              {                

        Console.WriteLine("{0}", myEnumerator.Current);

             }

             Console.Read();

}

可以看到,执行结果完全一样,编译器在处理foreach语句块时,就是编译为与以上类似的等价代码的(对GetEnumerator()的调用);foreach只是语法糖而已;这个方法(GetEnumerator())的返回值是一个接口(IEnumerator),也就是说,在这个方法体内需要出现一个实现了这个接口(System.Collections.IEnumerator)的类。那就先来看看这个接口的定义;

public interface IEnumerator

{  

    object Current{get;}  //注意这个属性是只读的,这也为什么我们不能在foreach块中改变集合元素的原因(集合元素内部的属性可以更改哦);

     bool MoveNext();      // 将枚举数推进到集合的下一个元素,成功返回true,失败返回false   

    void Reset();             //将枚举数设置为其初始位置,该位置位于集合中第一个元素之前

}

这个接口还有一个泛型版本

public interface  IEnumerator<T>:IDisposable,IEnumerator

{

     T Current{get;};  

    bool MoveNext();     

     void Reset();   

    void Dispose();

}

值得注意的是,泛型版本继承了IDispose接口,(关于泛型,理解的重点是,要区分开泛型的外壳类和外壳类包含的元素类,这个元素的类型是可变的,而本质上,真正可变的类型确是外壳类本身,这里的名词是我的杜撰,还算形象吧)其他的注释已经很清楚,不再过多解释;(这里涉及的接口比较多,名称还很类似,注意分辨哦) 而这个方法(System.Collections.IEnumerator GetEnumerator())本身却是在IEnumerable接口中定义,可以看到很多的集合类都继承实现了这个接口(IEnumerable),而事实上,要使用枚举器,这个接口并不是必须的,只要集合类有一个这个签名的的方法(System.Collections.IEnumerator GetEnumerator())就足够了,编译器根本不会检查集合类是否实现了接口IEnumberable,这看起来有点神奇,似乎对代码安全有点影响,事实上根本没有,仔细想一下就会明白,编译器默认会尝试将foreach转换为对 GetEnumerator()的调用,但它发现,类中根本没有这个方法,自然报错,注意,报错不是因为集合没有实现接口IEnumberable,而是因为集合没有提供GetEnumerator(),这个我讲的已经太罗嗦了,为了证明这一点,运行下面代码就可以证明;注意program类没有继承IEnumberable哦,  

class Program

{

            static void Main(string[] args)

            {

                   Program program=new Program();

                   foreach (string s in program)

                  {

                          Console.WriteLine(s);

                    }

                    Console.Read();

            }

            public  IEnumerator GetEnumerator()

            {

                        yield return"一";

                      yield return "二";

                        yield return "三";

             }

}

 

下面仔细解释一下刚才遗留的代码(再次贴来,请你把它贴到VS里执行一下)  

class Program    

{

        static void Main(string[] args)

        {            

      var ie = IE();

               Console.WriteLine("IE()已经调用了,你看到输出了吗?");

                 ie.MoveNext();

                 Console.Read();

        }

        public static IEnumerator IE()

       {

                while (true)

                {

                       Console.WriteLine("你在调用IE()时看不到我的执行,我不是并行方法啊哦\n哈哈,神奇吧,除非你用IE()的返回值调用了MoveNext()\n并且每次调用都可以看到我哦");               

                       yield return "任意类型都可以直接返回哦";

                }

        }

}

至于MS为什么要这样设计,如果你已经对LINQ有一定的理解,相信你应该可以理解这一点,LINQ的延迟查询正是利用这一点。关于LINQ的问题不是这篇博客的重点,反正记住这是MS特殊处理的就行啦。如果要自己实现枚举器,这个特性也是很有的。下面我们再看看这段代码的另一个值得关注的地方;yield return语句,可以看到IE()的返回值是 IEnumerator接口类型,而我们根本没有定义实现这个接口的类,那这个方法还怎么工作啊,应该直接编译错误才是啊,哈哈,要注意,返回值是通过yield return传递的哦,可是yield return里的类型和返回值类型不一样啊,相信很多新手都有这样的迷惑,其实这是又是语法糖而已,(MS对开发人员真是太好了,为了我们少击打键盘,费劲心思啊,但同时对于新手也真是不小的挑战,容易让新手浮于技术的表面,而看不到本质的原理)编译器会把yield return语句转化为一个实现了IEnumerator的类,这个类对于编程人员是透明的;每次yield return时都由这个类来收集结果,至于返回值,在调用这个这个方法时,已经立即返回了。可以把这个这个返回值看做一个特殊的句柄,只有用它调用MoveNext()时(MoveNext()会由foreach语句自动调用哦),该方法才真正执行,当然,yield return部分这时已经不再方法内了,否则就陷入死递归了,感兴趣的同学可以试试吧yield return返回语句分散开来,中间再夹杂一些其他语句,然后手工调用Movenext()观察一下奇怪的执行路线,好了,要介绍的已经差不多了,不明白孩子再好好理解理解吧,不然LINQ就没法学了,本文介绍的内容虽然很基础,但我认为对不少人还是有不少意义的,所以就大胆发到首页了哈,请大鸟们不要~~

 

 

0 0
原创粉丝点击