C#枚举数和迭代器
大道至简,始终认为简洁是一门优秀的编程语言的一个必要条件。相对来说,C#是比较简洁的,也越来越简洁。在C#中,一个关键字或者语法糖在编译器层面为我们做了很多乏味的工作,可能实现的是一个设计模式,甚至是一个算法。例如:lock关键字让用对象获取互斥锁从而实现线程同步,本质上是通过Monitor类来实现的,显然简洁很多。本文要讲的枚举数和迭代器在.net集合类被广泛使用,当然遵循着简洁的设计思想。
1.枚举数
1.1foreach的本质
我们知道,实现了IEnumerable接口的类型对象是可foreach遍历的,那么本质是什么呢?原来,在IEnumerable接口中定义这样一个方法:IEnumerator GetEnumerator(),
IEnumerator接口定义如下:
- public interface IEnumerator
- {
- // 摘要:
- // 获取集合中的当前元素。
- //
- // 返回结果:
- // 集合中的当前元素。
- //
- // 异常:
- // System.InvalidOperationException:
- // 枚举数定位在该集合的第一个元素之前或最后一个元素之后。
- object Current { get; }
- // 摘要:
- // 将枚举数推进到集合的下一个元素。
- //
- // 返回结果:
- // 如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
- //
- // 异常:
- // System.InvalidOperationException:
- // 在创建了枚举数后集合被修改了。
- bool MoveNext();
- //
- // 摘要:
- // 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
- //
- // 异常:
- // System.InvalidOperationException:
- // 在创建了枚举数后集合被修改了。
- void Reset();
- }
通过GetEnumerator方法,IEnumerable接口类型对象可以按需获取一个枚举数对象,枚举数可依次返回请求的集合元素作为迭代变量。
1.2枚举数的几种形式
上面说到实现了IEnumerable接口的类型对象是可foreach遍历的,这是充分不必要条件,实际上实现了IEnumerator GetEnumerator()方法的类型对象都是可枚举的。这里一共有三种形式:
- IEnumerator/IEnumerable非泛型接口形式
- IEnumerator<T>/IEnumerable<T>泛型接口形式
- 自实现形式
1.2.1IEnumerator/IEnumerable非泛型接口形式
我们来简单实现一下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Collections;
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- EnumerableEx arr = new object[] { "jello", , 'M' };
- foreach (var item in arr)
- {
- Console.WriteLine(item);
- }
- Console.ReadKey();
- }
- }
- class EnumerableEx : IEnumerable
- {
- object[] arr;
- public static implicit operator EnumerableEx(object[] _arr)
- {
- EnumerableEx _enum = new EnumerableEx();
- _enum.arr = new object[_arr.Length];
- for (int i = ; i < _arr.Length; i++)
- {
- _enum.arr[i] = _arr[i];
- }
- return _enum;
- }
- public IEnumerator GetEnumerator()
- {
- return new EnumeratorEx(arr);
- }
- }
- class EnumeratorEx : IEnumerator
- {
- private int _pos = -;//当前元素位置
- private object[] _array;//要遍历的数组
- //构造函数
- public EnumeratorEx(object[] array)
- {
- _array = array;
- }
- //迭代变量
- public object Current
- {
- get
- {
- if (_pos == - || _pos >= _array.Length)
- throw new InvalidOperationException();
- return _array[_pos];
- }
- }
- //移位
- public bool MoveNext()
- {
- if (_pos < _array.Length - )
- {
- _pos++;
- return true;
- }
- else
- return false;
- }
- //重置
- public void Reset()
- {
- _pos = -;
- }
- }
- }
这里首先向IEnumerable提供了需遍历的数组(使用了隐式用户自定义转换),在foreach中首先会调用GetEnumerator方法,然后MoveNext移到下一个位置,Current即为迭代变量。
1.2.2IEnumerator<T>/IEnumerable<T>泛型接口形式
非泛型接口形式中迭代变量是Object类型(非类型安全),这无法避免装箱和拆箱,尤其是当元素个数很多的时候,性能会消耗很大,因此引入了泛型接口形式。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace ConsoleApplication1
- {
- class Program1
- {
- public static void Main(string[] args)
- {
- EnumerableEx<int> arr = new int[] { , , };
- foreach (var item in arr)
- {
- Console.WriteLine(item);
- }
- Console.ReadKey();
- }
- }
- class EnumerableEx<T> : IEnumerable<T>
- {
- T[] arr;
- public static implicit operator EnumerableEx<T>(T[] _arr)
- {
- EnumerableEx<T> _enum = new EnumerableEx<T>();
- _enum.arr = new T[_arr.Length];
- for (int i = ; i < _arr.Length; i++)
- {
- _enum.arr[i] = _arr[i];
- }
- return _enum;
- }
- public IEnumerator<T> GetEnumerator()
- {
- return new EnumeratorEx<T>(arr);
- }
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return new EnumeratorEx<T>(arr);
- }
- }
- class EnumeratorEx<T> : IEnumerator<T>
- {
- private int _pos = -;//当前元素位置
- private T[] _array;//要遍历的数组
- public EnumeratorEx(T[] array)
- {
- _array = array;
- }
- public T Current
- {
- get
- {
- if (_pos == - || _pos >= _array.Length)
- throw new InvalidOperationException();
- return _array[_pos];
- }
- }
- public void Dispose()
- {
- //可用于释放非托管资源
- }
- object System.Collections.IEnumerator.Current
- {
- get
- {
- if (_pos == - || _pos >= _array.Length)
- throw new InvalidOperationException();
- return _array[_pos];
- }
- }
- public bool MoveNext()
- {
- if (_pos < _array.Length - )
- {
- _pos++;
- return true;
- }
- else
- return false;
- }
- public void Reset()
- {
- _pos = -;
- }
- }
- }
和非泛型接口形式基本一样,IEnumerable<T>除了继承IEnumerable接口,还继承了IDisposable接口用来释放非托管资源。
1.2.3自实现形式
自实现形式不继承自上面的接口,自定义一个实现GetEnumerator()的类和一个实现Current和MoveNext的类,好处是更加灵活,缺点是通用性差。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Collections;
- namespace ConsoleApplication1
- {
- class Program2
- {
- public static void Main(string[] args)
- {
- MyEnumerable<int> arr = new int[] { , , };
- foreach (var item in arr)
- {
- Console.WriteLine(item);
- }
- Console.ReadKey();
- }
- }
- class MyEnumerable<T>
- {
- T[] arr;
- public static implicit operator MyEnumerable<T>(T[] _arr)
- {
- MyEnumerable<T> _enum = new MyEnumerable<T>();
- _enum.arr = new T[_arr.Length];
- for (int i = ; i < _arr.Length; i++)
- {
- _enum.arr[i] = _arr[i];
- }
- return _enum;
- }
- public MyEnumerator<T> GetEnumerator()
- {
- return new MyEnumerator<T>(arr);
- }
- }
- class MyEnumerator<T>
- {
- private int _pos = -;//当前元素位置
- private T[] _array;//要遍历的数组
- public MyEnumerator(T[] array)
- {
- _array = array;
- }
- public T Current
- {
- get
- {
- if (_pos == - || _pos >= _array.Length)
- throw new InvalidOperationException();
- return _array[_pos];
- }
- }
- public bool MoveNext()
- {
- if (_pos < _array.Length - )
- {
- _pos++;
- return true;
- }
- else
- return false;
- }
- public void Reset()
- {
- _pos = -;
- }
- }
- }
需要注意的是:Reset方法并不是必须要实现的。
2.迭代器
2.1What's 迭代器
迭代器是在.net2.0中引入的一种结构,旨在更加简单地创建枚举数和可枚举类型。先来看一个简单例子:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace ConsoleApplication1
- {
- class Program3
- {
- public static void Main(string[] args)
- {
- Iteration iteration = new Iteration();
- foreach (var item in iteration)
- {
- Console.WriteLine(item);
- }
- Console.ReadKey();
- }
- }
- class Iteration
- {
- public IEnumerator<int> GetNums()
- {
- for (int i = ; i < ; i++)
- {
- yield return i;
- }
- }
- public IEnumerator<int> GetEnumerator()
- {
- return GetNums();
- }
- }
- }
上面是通过yield return来获取枚举数的,通过运行结果发现,循环体内的yield return并没有在第一次迭代中返回,而是每次访问迭代变量时都能获取一个新元素值。
由一个或多个yield语句组成的代码块称为迭代器块,它和普通的代码块不同,并不是依次执行的,仅当需要获取迭代变量值时执行一次。
上面举了一个使用迭代器来创建枚举数的例子,其实,使用迭代器还可以创建可枚举类型:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace ConsoleApplication1
- {
- class Program3
- {
- public static void Main(string[] args)
- {
- Iteration iteration = new Iteration();
- foreach (var item in iteration)
- {
- Console.WriteLine(item);
- }
- Console.ReadKey();
- }
- }
- class Iteration
- {
- public IEnumerable<int> GetNums()
- {
- for (int i = ; i < ; i++)
- {
- yield return i;
- }
- }
- public IEnumerator<int> GetEnumerator()
- {
- return GetNums().GetEnumerator();
- }
- }
- }
上面的两段代码有两个地方的不同:一是GetNums方法返回类型不同;二是GetEnumerator方法实现的不同。
2.2迭代器本质
我们使用简单的yield return就可以创建枚举数或可枚举类型,那么在编译器层面究竟做了些什么呢?通过IL代码可以管中窥豹:
原来,编译器在遇到迭代器块时会生成一个嵌套类,这个类实现了IEnumerable<T>和IEnumerator<T>等接口,在这个类中维护了一个拥有四个状态的状态机:
- Before:首次调用MoveNext的初始状态
- Running:调用MoveNext后进入该状态。在这个状态中,枚举数检测并设置下一项的位置。在遇到yield return、yield break或迭代器块结束时退出状态
- Suspended:状态机等待下次调用MoveNext的状态
- After:没有更多项可以枚举
如果状态机在Before或Suspended状态有一次MoveNext调用就进入Running状态。在Running状态中检测集合的下一项并设置位置。如果有更多项状态机会转入Suspended状态,如果没有更多项则转入并保持在After状态,如图所示:
3.总结
总而言之,其实是对迭代器设计模式的运用简化。既不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据,这是迭代器设计模式的思想。
C#枚举数和迭代器的更多相关文章
- C#-14 枚举器和迭代器
一 枚举器和可枚举类型 当我们为数组使用foreach语句时,这个语句为我们依次取出了数组中的每一个元素. var arrInt = new int[] { 11, 12, 13, 14 }; for ...
- C#的枚举数(Enumerator)和可枚举类型(Enumerable)
数组可以被foreach语句遍历数组中的元素,原因是数组可以按需提供一个叫做枚举数(enumerator)的对象.枚举数可以依次返回请求的数组的元素. 对于有枚举数的类型而言,必须有一个方法来获取它们 ...
- C#图解教程 第十八章 枚举器和迭代器
枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...
- C# 枚举器和迭代器
一.枚举器(enumerator)和可枚举类型(enumeration) 我们都知道foreach语句可以用来遍历数组中的元素,但你有没有想过为什么它可以被foreach处理呢? 这是因为数组可以按需 ...
- 【C#】IEnumrator的枚举数和IEnumerable接口
声明IEnumerator的枚举数 要创建非泛型接口的枚举数,必须声明实现IEnumerator接口的类,IEnumerator接口有如下特性: 1.她是System.Collections命名空间的 ...
- 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释
适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...
- 实现自定义集合的可枚举类型(IEnumerable)和枚举数(IEnumerator )
下面的代码示例演示如何实现自定义集合的 IEnumerable 和 IEnumerator 接口: using System; using System.Collections; using Syst ...
- 暴力枚举-数长方形(hdu5258)
数长方形 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- C#知识点-枚举器和迭代器
一.几个基本概念的理解 问题一:为什么数组可以使用foreach输出各元素 答:数组是可枚举类型,它实现了一个枚举器(enumerator)对象:枚举器知道各元素的次序并跟踪它们的位置,然后返回请求的 ...
随机推荐
- Java垃圾回收机制以及内存泄露
1.Java的内存泄露介绍 首先明白一下内存泄露的概念:内存泄露是指程序执行过程动态分配了内存,可是在程序结束的时候这块内存没有被释放,从而导致这块内存不可用,这就是内存 泄露,重新启动计算机能够解决 ...
- eclipse 安装vrapper vim插件
http://vrapper.sourceforge.net/update-site/stable 如果安装不上,设置下代理./window/pereference/network* - manul
- HDU 1164 Eddy's research I【素数筛选法】
思路:将输入的这个数分成n个素数的相乘的结果,用一个数组存储起来.之后再输出就能够了 Eddy's research I Time Limit: 2000/1000 MS (Java/Others) ...
- hdu4635(最多加多少边,使得有向图不是强连通图)
连边的最后肯定是两个集合x,yx集合的每个元素,到y集合中的每个元素都是单向的边x集合,和y集合都是完全图设a为x集合的点的个数, b为y集合的那么答案就是 a * b + a*(a-1) + b*( ...
- hdu1513 (滚动数据压缩空间)
给定一个字符串,问最少添加多少个字符可以使得这个字符串变成回文串 if(str[i]==str[j]) dp[i][j] = dp[i+1][j-1] else dp[i][j] = min(dp[i ...
- Android App 内存泄漏Handler
Android App 内存泄露之Handler Handler也是造成内存泄露的一个重要的源头,主要Handler属于TLS(Thread Local Storage)变量,生命周期和Activit ...
- 数据市中心全省中国mysql脚本
1.查尔斯省 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2h6aGFvY2hhbw==/font/5a6L5L2T/fontsize/400/fill ...
- Swing多线程编程(转)
关键字: Swing,多线程,GUI,SwingWorker 摘要: 本文论述了怎样开发多线程的Swing程序,从而提高Swing程序的响应速度和性能. 近期,我将推出一系列研究Swing程序 ...
- C#实现远程机器管理
原文:C#实现远程机器管理 目前处于待离职状态,原先所有的工作都在进行交接,过程当中不乏有很多先前整理的和动手尝试实现的功能:我的主页中已经列出来一部分内容,有兴趣的可以前往看一看. 接下来的内容主要 ...
- 下的生产环境was重新启动不同意,怎么做?
前一段时间上线.遇到一个jndi问题,它是如何是个问题?它是在测试环境中的原始没有问题,在生产环境中,您无法连接生产数据库,然后发现问题,那是,ibm工具生成在测试环境中自己主动的连接jndi资源文件 ...