大道至简,始终认为简洁是一门优秀的编程语言的一个必要条件。相对来讲,C#是比较简洁的,也愈来愈简洁。在C#中,一个关键字或者语法糖在编译器层面为咱们作了不少乏味的工做,可能实现的是一个设计模式,甚至是一个算法。例如:lock关键字让用对象获取互斥锁从而实现线程同步,本质上是经过Monitor类来实现的,显然简洁不少。本文要讲的枚举数和迭代器在.net集合类被普遍使用,固然遵循着简洁的设计思想。算法
咱们知道,实现了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接口类型对象能够按需获取一个枚举数对象,枚举数可依次返回请求的集合元素做为迭代变量。数组
上面说到实现了IEnumerable接口的类型对象是可foreach遍历的,这是充分没必要要条件,实际上实现了IEnumerator GetEnumerator()方法的类型对象都是可枚举的。这里一共有三种形式:安全
咱们来简单实现一下:编程语言
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", 22, '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 = 0; i < _arr.Length; i++) { _enum.arr[i] = _arr[i]; } return _enum; } public IEnumerator GetEnumerator() { return new EnumeratorEx(arr); } } class EnumeratorEx : IEnumerator { private int _pos = -1;//当前元素位置 private object[] _array;//要遍历的数组 //构造函数 public EnumeratorEx(object[] array) { _array = array; } //迭代变量 public object Current { get { if (_pos == -1 || _pos >= _array.Length) throw new InvalidOperationException(); return _array[_pos]; } } //移位 public bool MoveNext() { if (_pos < _array.Length - 1) { _pos++; return true; } else return false; } //重置 public void Reset() { _pos = -1; } } }
这里首先向IEnumerable提供了需遍历的数组(使用了隐式用户自定义转换),在foreach中首先会调用GetEnumerator方法,而后MoveNext移到下一个位置,Current即为迭代变量。ide
非泛型接口形式中迭代变量是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[] { 1, 2, 3 }; 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 = 0; 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 = -1;//当前元素位置 private T[] _array;//要遍历的数组 public EnumeratorEx(T[] array) { _array = array; } public T Current { get { if (_pos == -1 || _pos >= _array.Length) throw new InvalidOperationException(); return _array[_pos]; } } public void Dispose() { //可用于释放非托管资源 } object System.Collections.IEnumerator.Current { get { if (_pos == -1 || _pos >= _array.Length) throw new InvalidOperationException(); return _array[_pos]; } } public bool MoveNext() { if (_pos < _array.Length - 1) { _pos++; return true; } else return false; } public void Reset() { _pos = -1; } } }
和非泛型接口形式基本同样,IEnumerable<T>除了继承IEnumerable接口,还继承了IDisposable接口用来释放非托管资源。性能
自实现形式不继承自上面的接口,自定义一个实现GetEnumerator()的类和一个实现Current和MoveNext的类,好处是更加灵活,缺点是通用性差。spa
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[] { 1, 2, 3 }; 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 = 0; 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 = -1;//当前元素位置 private T[] _array;//要遍历的数组 public MyEnumerator(T[] array) { _array = array; } public T Current { get { if (_pos == -1 || _pos >= _array.Length) throw new InvalidOperationException(); return _array[_pos]; } } public bool MoveNext() { if (_pos < _array.Length - 1) { _pos++; return true; } else return false; } public void Reset() { _pos = -1; } } }
须要注意的是:Reset方法并非必需要实现的。
迭代器是在.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 = 0; i < 10; 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 = 0; i < 10; i++) { yield return i; } } public IEnumerator<int> GetEnumerator() { return GetNums().GetEnumerator(); } } }
上面的两段代码有两个地方的不一样:一是GetNums方法返回类型不一样;二是GetEnumerator方法实现的不一样。
咱们使用简单的yield return就能够建立枚举数或可枚举类型,那么在编译器层面究竟作了些什么呢?经过IL代码能够管中窥豹:
原来,编译器在遇到迭代器块时会生成一个嵌套类,这个类实现了IEnumerable<T>和IEnumerator<T>等接口,在这个类中维护了一个拥有四个状态的状态机:
若是状态机在Before或Suspended状态有一次MoveNext调用就进入Running状态。在Running状态中检测集合的下一项并设置位置。若是有更多项状态机会转入Suspended状态,若是没有更多项则转入并保持在After状态,如图所示:
总而言之,实际上是对迭代器设计模式的运用简化。既不暴露集合的内部结构,又可以让外部代码透明的访问集合内部的数据,这是迭代器设计模式的思想。