黑马程序猿——C#枚举器深入解析
------- 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#枚举器深入解析相关推荐
- 七、python-PySpark篇(黑马程序猿-python学习记录)
黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...
- 六、python操作mysql篇(黑马程序猿-python学习记录)
黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...
- 五、python-地图可视化篇(黑马程序猿-python学习记录)
黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...
- 二、python基础语法篇(黑马程序猿-python学习记录)
黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...
- 三、python基础语法进阶篇(黑马程序猿-python学习记录)
黑马程序猿的python学习视频:https://www.bilibili.com/video/BV1qW4y1a7fU/ ====================================== ...
- 黑马程序员————高新技术————类加载器
----------------------ASP.Net+Android+IOS开发----------------------期待与您交流! 类加载器 Java虚拟机中可以安装多个类的加载器,系统 ...
- 黑马程序员___java类加载器
-----------android培训.java培训.java学习型技术博客.期待与您交流! --------- 类加载器基本概念 顾名思义,类加载器(class loader)用来加载 Java ...
- 基于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 ...
- 黑马程序员C语言基础(第八天)复合类型(自定义类型)(结构体)、共用体(联合体)、枚举enum、 typedef
黑马程序员C语言基础(第一天) 黑马程序员C语言基础(第二天) 黑马程序员C语言基础(第三天) 黑马程序员C语言基础(第四天)数据类型 黑马程序员C语言基础(第五天)运算符与表达式.程序流程结构.数组 ...
最新文章
- 算法 - 求一个正整数的二进制表示中1的个数(C++)
- 阿里云VGN5i虚拟化GPU服务器价格更低的GPU计算服务
- 后续升级鸿蒙系统,荣耀部分机型后续将支持升级为鸿蒙系统
- java编程石头剪刀布_java 开发的石头,剪刀,布的游戏 demo
- Spring-Jpa : @MappedSuperclass的作用
- selenium 定位方式2
- shell date cal
- java中对象的克隆
- php带图片的每日单词,GRE背单词-每日十个单词(第一天) - 英语家园
- MATLAB数值计算学习笔记(二)误差理论和非线性方程求解
- 服务器主机如何多开虚拟机,服务器主机多开虚拟机
- 58子站安居,经纪人营销管理平台登录接口加密逆向
- 推荐系统[八]:推荐系统常遇到问题和解决方案[物品冷启动问题、多目标平衡问题、数据实时性问题等]
- 【研0需要知道的那些事01】如何判断期刊是否为核心期刊,知网导出参考文献越来越多怎么办?
- JavaOOP面试题(108道)
- iOS:xib中加载自定义的xib控件, 解决死循环
- Tomcat 基础整理
- 云南省二级c计算机考试试题,2015云南省计算机等级考试试题 二级C试题最新考试试题库(完整版)...
- 投票 | 给烤仔一个和星球君一起胖的机会吧
- 计网真题:信道利用率计算