什么是IEnumerable?
IEnumerable及IEnumerable的泛型版本IEnumerable<T>是一個接口,它只含有一個方法GetEnumerator。Enumerable這個靜態類型含有很多擴展方法,其擴展的目標是IEnumerable<T>。
實現了這個接口的類可以使用Foreach關鍵字進行迭代(迭代的意思是對於一個集合,可以逐一取出元素並遍歷之)。實現這個接口必須實現方法GetEnumerator。
實現一個繼承IEnumerable的類型等同於實現方法GetEnumerator。想知道如何實現方法GetEnumerator,不妨思考下實現了GetEnumerator之后的類型在Foreach之下的行為:
- 可以獲得第一個或當前成員
- 可以移動到下一個成員
- 可以在集合沒有下一個成員時退出循環。
假設我們有一個很簡單的Person類(例子來自MSDN):
public class Person { public Person(string fName, string lName) { FirstName = fName; LastName = lName; } public string FirstName; public string LastName; }
然后我們想構造一個沒有實現IEnumerable的類型,其儲存多個Person,然后再對這個類型實現IEnumerable。這個類型實際上的作用就相當於Person[]或List<Person>,但我們不能使用它們,因為它們已經實現了IEnumerable,故我們構造一個People類,模擬很多人(People是Person的復數形式)。這個類型允許我們傳入一組Person的數組。所以它應當有一個Person[]類型的成員,和一個構造函數,其可以接受一個Person[],然后將Person[]類型的成員填充進去作為初始化。
//People類就是Person類的集合 //但我們不能用List<Person>或者Person[],因為他們都實現了IEnumerable //我們要自己實現一個IEnumerable //所以請將People類想象成List<Person>或者類似物 public class People : IEnumerable { private readonly Person[] _people; public People(Person[] pArray) { //構造一個Person的集合 _people = new Person[pArray.Length]; for (var i = 0; i < pArray.Length; i++) { _people[i] = pArray[i]; } } //實現IEnumerable需要實現GetEnumerator方法 public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }
我們的主函數應當是:
public static void Main(string[] args) { //新的Person數組 Person[] peopleArray = { new Person("John", "Smith"), new Person("Jim", "Johnson"), new Person("Sue", "Rabon"), }; //People類實現了IEnumerable var peopleList = new People(peopleArray); //枚舉時先訪問MoveNext方法 //如果返回真,則獲得當前對象,返回假,就退出此次枚舉 foreach (Person p in peopleList) Console.WriteLine(p.FirstName + " " + p.LastName); } 復制代碼
但現在我們的程序不能運行,因為我們還沒實現GetEnumerator方法。
實現方法GetEnumerator
GetEnumerator方法需要一個IEnumerator類型的返回值,這個類型是一個接口,所以我們不能這樣寫:
return new IEnumerator();
因為我們不能實例化一個接口。我們必須再寫一個類PeopleEnumerator,它繼承這個接口,實現這個接口所有的成員:Current屬性,兩個方法MoveNext和Reset。於是我們的代碼又變成了這樣:
//實現IEnumerable需要實現GetEnumerator方法 public IEnumerator GetEnumerator() { return new PeopleEnumerator(); }
在類型中:
public class PeopleEnumerator : IEnumerator { public bool MoveNext() { throw new NotImplementedException(); } public void Reset() { throw new NotImplementedException(); } public object Current { get; } }
現在問題轉移為實現兩個方法,它們的功能看上去一目了然:一個負責將集合中Current向后移動一位,一個則將Current初始化為0。我們可以查看IEnumerator元數據,其解釋十分清楚:
- Enumerator代表一個類似箭頭的東西,它指向這個集合當前迭代指向的成員
- IEnumerator接口類型對非泛型集合實現迭代
- Current表示集合當前的元素,我們需要用它僅有的get方法取得當前元素
- MoveNext方法根據Enumerator是否可以繼續向后移動返回真或假
- Reset方法將Enumerator移到集合的開頭
通過上面的文字,我們可以理解GetEnumerator方法,就是獲得當前Enumerator指向的成員。我們引入一個整型變量position來記錄當前的位置,並且先試着寫下:
public class PeopleEnumerator : IEnumerator { public Person[] _peoples; public object Current { get; } //當前位置 public int position; //構造函數接受外部一個集合並初始化自己內部的屬性_peoples public PeopleEnumerator(Person[] peoples) { _peoples = peoples; } //如果沒到集合的尾部就移動position,返回一個bool public bool MoveNext() { if (position < _peoples.Length) { position++; return true; } return false; } public void Reset() { position = 0; } }
這看上去好像沒問題,但一執行之后卻發現:
- 當執行到MoveNext方法時,position會先增加1,這導致第一個元素(在位置0)會被遺漏,故position的初始值應當為-1而不是0
- 當前位置變量position顯然應該是私有的
- 需要編寫Current屬性的get方法取出當前位置(position)上的集合成員
通過不斷的調試,最后完整的實現應當是:
public class PeopleEnumerator : IEnumerator { public Person[] People; //每次運行到MoveNext或Reset時,利用get方法自動更新當前位置指向的對象 object IEnumerator.Current { get { try { //當前位置的對象 return People[_position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } //當前位置 private int _position = -1; public PeopleEnumerator(Person[] people) { People = people; } //當程序運行到foreach循環中的in時,就調用這個方法獲得下一個person對象 public bool MoveNext() { _position++; //返回一個布爾值,如果為真,則說明枚舉沒有結束。 //如果為假,說明已經到集合的結尾,就結束此次枚舉 return (_position < People.Length); } public void Reset() => _position = -1; }
為什么當程序運行到in時,會呼叫方法MoveNext呢?我們並沒有直接調用這個方法啊?當你試圖查詢IL時,就會得到答案。實際上下面兩段代碼的作用是相同的:
foreach (T item in collection) { ... }
IEnumerator<T> enumerator = collection.GetEnumerator(); while (enumerator.MoveNext()) { T item = enumerator.Current; ... }
轉載:https://www.cnblogs.com/haoyifei/p/5768379.html