大道至簡,始終認爲簡潔是一門優秀的編程語言的一個必要條件。相對來講,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狀態,如圖所示:
總而言之,實際上是對迭代器設計模式的運用簡化。既不暴露集合的內部結構,又可以讓外部代碼透明的訪問集合內部的數據,這是迭代器設計模式的思想。