------- 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就没法学了,本文介绍的内容虽然很基础,但我认为对不少人还是有不少意义的,所以就大胆发到首页了哈,请大鸟们不要~~

黑马程序猿——C#枚举器深入解析相关推荐

  1. 七、python-PySpark篇(黑马程序猿-python学习记录)

    黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...

  2. 六、python操作mysql篇(黑马程序猿-python学习记录)

    黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...

  3. 五、python-地图可视化篇(黑马程序猿-python学习记录)

    黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...

  4. 二、python基础语法篇(黑马程序猿-python学习记录)

    黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...

  5. 三、python基础语法进阶篇(黑马程序猿-python学习记录)

    黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...

  6. 黑马程序员————高新技术————类加载器

    ----------------------ASP.Net+Android+IOS开发----------------------期待与您交流! 类加载器 Java虚拟机中可以安装多个类的加载器,系统 ...

  7. 黑马程序员___java类加载器

    -----------android培训.java培训.java学习型技术博客.期待与您交流! --------- 类加载器基本概念 顾名思义,类加载器(class loader)用来加载 Java ...

  8. 基于VueAxios制作音乐播放器(bilibili黑马程序员Vue入门学习记录)

    目录 使用Vue制作一个音乐播放器 前言 Vue Vue导入 Vue挂载 Vue指令 v-text v-html v-on v-show v-if v-bind v-for v-model axios ...

  9. 黑马程序员C语言基础(第八天)复合类型(自定义类型)(结构体)、共用体(联合体)、枚举enum、 typedef

    黑马程序员C语言基础(第一天) 黑马程序员C语言基础(第二天) 黑马程序员C语言基础(第三天) 黑马程序员C语言基础(第四天)数据类型 黑马程序员C语言基础(第五天)运算符与表达式.程序流程结构.数组 ...

最新文章

  1. 算法 - 求一个正整数的二进制表示中1的个数(C++)
  2. 阿里云VGN5i虚拟化GPU服务器价格更低的GPU计算服务
  3. 后续升级鸿蒙系统,荣耀部分机型后续将支持升级为鸿蒙系统
  4. java编程石头剪刀布_java 开发的石头,剪刀,布的游戏 demo
  5. Spring-Jpa : @MappedSuperclass的作用
  6. selenium 定位方式2
  7. shell date cal
  8. java中对象的克隆
  9. php带图片的每日单词,GRE背单词-每日十个单词(第一天) - 英语家园
  10. MATLAB数值计算学习笔记(二)误差理论和非线性方程求解
  11. 服务器主机如何多开虚拟机,服务器主机多开虚拟机
  12. 58子站安居,经纪人营销管理平台登录接口加密逆向
  13. 推荐系统[八]:推荐系统常遇到问题和解决方案[物品冷启动问题、多目标平衡问题、数据实时性问题等]
  14. 【研0需要知道的那些事01】如何判断期刊是否为核心期刊,知网导出参考文献越来越多怎么办?
  15. JavaOOP面试题(108道)
  16. iOS:xib中加载自定义的xib控件, 解决死循环
  17. Tomcat 基础整理
  18. 云南省二级c计算机考试试题,2015云南省计算机等级考试试题 二级C试题最新考试试题库(完整版)...
  19. 投票 | 给烤仔一个和星球君一起胖的机会吧
  20. 计网真题:信道利用率计算

热门文章

  1. 正则表达式贪婪和非贪婪模式
  2. DB文件会生成在哪里以及生成的个数是多少?
  3. mysql 聚合函数求乘积_mysql-聚合函数
  4. electron+vue项目打包时修改注册表功能
  5. IP冲突导致访问不了请求
  6. 关于cv::imread读取图片类型的初探
  7. 常用的5款企业内部聊天软件,助力团队沟通与协同
  8. .thumbnails
  9. BroadcastReceiver中onReceive()方法中需要注意什么
  10. 基于JSP的毕业设计选题管理系统设计与实现