IEnumerator和IEnumerable的作用
其實IEnumerator和IEnumerable的作用很簡單,就是讓除數組和集合之外的類型也能支持foreach循環,至於foreach循環,如果不清楚,請參考C# foreach循環較for循環的優勢與劣勢
代碼如下:
static void Main(string[] args) { CatList cats = new CatList(); foreach (var cat in cats) { Console.WriteLine(cat.NickName); } } public class CatList { public string NickName { get; set; } public int Age { get; set; } }
以上代碼說明自定義集合類型(假設CatList是集合類型)是無法使用foreach進行循環的.
原因是C#中自定義集合類型要實現foreach的功能,必須通過IEnumerator和IEnumerable兩個接口來實現!
1、通過IEnumerator和IEnumerable兩個接口實現自定義集合類型的foreach循環功能. 非泛型版本
第一步:實現自定義集合類型實現IEnumerable接口,實現該接口的字面意思可以理解為:自定義集合類型實現了該接口,就擁有了"可枚舉的功能".代碼如下:
實現了IEnumerable接口之后,發現該接口規定必須返回一個IEnumerator接口(迭代器對象).ok,那么就必須返回一個IEnumerator,那么它是什么呢?別急,下面開始介紹這個接口!
第二步:通過IEnumerable要求實現的GetEnumerator()方法返回一個IEnumerator(迭代器對象),實現該接口必須實現以下三個方法/屬性:
(1)、MoveNext() ---將當前遍歷的枚舉數推進到集合的下一個.
注:如果 MoveNext 越過集合的末尾,則枚舉數將被放置在此集合中最后一個元素的后面,而且 MoveNext 返回 false。當枚舉數位於此位置時,對MoveNext 的后續調用也返回 false。如果最后一次調用 MoveNext 返回 false,則調用 Current 會引發異常。若要再次將 Current 設置為集合的第一個元素,可以調用 Reset,然后再調用 MoveNext。
(2)、Current屬性 ---返回正在遍歷的集合中的元素,注:該屬性沒有set方法,所以在循環遍歷的時候不能設置值.
(3)、Reset() ---重置當前正在遍歷的集合中元素的索引.
第三步:具體實現
在介紹完上面兩個接口之后,開始具體的實現,現在需要編寫一個People類,該類是一個Person集合,要求People類擁有foreach循環的功能,代碼如下:
public class People : IEnumerable { private Person[] persons; public People(Person[] persons) { Persons = persons; } public Person[] Persons { get => persons; set => persons = value; } public IEnumerator GetEnumerator() { return new PersonEnum(Persons); } public class PersonEnum : IEnumerator { private Person[] perons; private int _index=-1; public PersonEnum(Person[] persons) { Perons = persons; } public object Current => Perons[_index]; public Person[] Perons { get => perons; set => perons = value; } public bool MoveNext() { _index++; if (_index < perons.Length) return true; else return false; } public void Reset() { _index = 0; } } }
第四步:驗證代碼,代碼如下:
Person[] persons ={ new Person(){FirstName="Stephen",LastName="Curry"}, new Person(){FirstName="Lebron",LastName="James"}, new Person(){FirstName="Kobe",LastName="Brant"} }; People people = new People(persons); foreach (var p in people) { Console.WriteLine(((Person)p).LastName); }
第五步:分析原理
總結分析下上面的代碼,實現foreach代碼的基本原理如下:
1、編寫自定義集合類,實現IEnumerable接口,通過GetEnumerator()方法返回一個迭代器對象實例.
2、通過自定義集合類的構造函數,或者方法,或者屬性(都可以),初始化一個類數組 !Important
3、將初始化完的類數組作為參數傳遞給迭代器類
4、編寫迭代器類,create 構造函數,接收自定義集合類初始化完的類數組
5、實現IEnumerator(迭代器)接口,實現對應的三個方法,通過編寫三個方法發現,其實迭代器就是簡單的對數組進行的操作
第六步:執行自定義集合的循環
執行方式有兩種:
(1)、foreach
Person[] persons ={ new Person(){FirstName="Stephen",LastName="Curry"}, new Person(){FirstName="Lebron",LastName="James"}, new Person(){FirstName="Kobe",LastName="Brant"} }; People people = new People(persons); foreach (Person p in persons) { Console.WriteLine(p.LastName); } Console.ReadKey();
(2)、通過自定義集合類的GetEnumerator()方法
Person[] persons ={ new Person(){FirstName="Stephen",LastName="Curry"}, new Person(){FirstName="Lebron",LastName="James"}, new Person(){FirstName="Kobe",LastName="Brant"} }; People people = new People(persons); IEnumerator er = people.GetEnumerator(); while (er.MoveNext()) { Console.WriteLine(((Person)er.Current).LastName); }
分析兩種不同的調用方式,foreach語句可以理解為是第二種方式的語法糖.
2、通過IEnumerable<T>和IEnumerator<T>實現自定義集合類,並實現簡單的添加功能
class Program { static void Main(string[] args) { var list = new CustomList<int>(2); list.Add(1); list.Add(2); list.Add(2); var p = new int[] { 1, 2, 2 }; var list1 = new CustomList<int>(p); Console.WriteLine(list.Count+"..."+list1.Count); Console.ReadKey(); } } public class CustomList<T> : IEnumerable<T> { private T[] _ts; private int _index = 0; public CustomList(T[] ts) { _ts = ts; _index = ts.Length; } public CustomList(int capcity) { _ts = new T[capcity]; } public int Count => _index; public void Add(T t) { if (_index >= _ts.Length) { //如果調用Add方法導致數組的長度大於我們給定的長度 //則創建一個新的數組,並將其長度擴大為原來的兩倍 T[] newArr = new T[_ts.Length * 2]; //將原來數組中的數據拷貝到新的數組中 Array.Copy(_ts, newArr, _ts.Length); //使_ts指向新的數組 _ts = newArr; } _ts[_index++] = t; } public IEnumerator<T> GetEnumerator() { return new CustomEnumerator<T>(_ts); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } public class CustomEnumerator<T> : IEnumerator<T> { private T[] _ts; private int _position = -1; public CustomEnumerator(T[] ts) { _ts = ts; } public T Current => _ts[_position]; object IEnumerator.Current => this.Current; public bool MoveNext() { _position++; if (_position < _ts.Length) return true; return false; } public void Reset() { _position = 0; } public void Dispose() { throw new NotImplementedException(); } }