枚舉是迭代一個集合中的數(shù)據(jù)項(xiàng)的過程。
我們經(jīng)常使用的大多數(shù)集合實(shí)際上都已經(jīng)實(shí)現(xiàn)了枚舉的接口IEnumerable和IEnumerator接口,這樣才能使用foreach迭代,有些是含有某種抽象了枚舉細(xì)節(jié)的接口:ArrayList類型有索引,BitArray有Get方法,哈希表和字典有鍵和值..........其實(shí)他們都已經(jīng)實(shí)現(xiàn)了IEnumerable和IEnumerator接口。所以一切的集合和數(shù)組都可以用IEnumerable或者IEnumerable<T>接口來定義。
1
2
3
4
5
6
7
8
9
10
|
IEnumerable lists1 = new int [] { 3, 4, 5 }; foreach (var val in lists1) { Console.WriteLine(val); } IEnumerable< int > lists2= new int []{1,2,3}; foreach (var val in lists2) { Console.WriteLine(val); } |
下面講解一下 自己來定義可枚舉類型(簡單說就是自己定義的 ,可以進(jìn)行foreach迭代的集合):
因?yàn)槊杜e非常有好處,可以消除很多的錯誤,所以實(shí)現(xiàn)某種標(biāo)準(zhǔn)是有好處的。這種標(biāo)準(zhǔn)就是IEnumerable和IEnumerator接口,必須實(shí)現(xiàn)了它才能夠使用foreach迭代,才能真正算是一個自己定義的,功能健全的集合。
我們自己建立的可枚舉類型必須實(shí)現(xiàn)IEnumerable和IEnumerator接口(其實(shí)兩者都有一個泛型實(shí)現(xiàn))。
IEnumerable接口含有一個方法,該方法返回一個枚舉器對象,枚舉器對象實(shí)現(xiàn)了IEnumerator接口(實(shí)際上可以認(rèn)為繼承和實(shí)現(xiàn)了IEnumerator的接口的類的對象就是枚舉器對象),可以用它來進(jìn)行迭代。
下面是兩個接口的定義(系統(tǒng)早已經(jīng)定義好):
1
2
3
4
|
public interface IEnumerable { IEnumerator GetEnumerator(); } |
該接口只有一個GetEnumerator的方法,返回一個枚舉器,用于枚舉集合中的元素。
1
2
3
4
5
6
|
public interface IEnumerator { object Current { get ; }; //Current屬性返回集合的當(dāng)前元素 bool MoveNext(); //將枚舉移動到下一位 void Reset(); //使枚舉回到開頭 } |
凡是繼承和實(shí)現(xiàn)了上面這個接口的類對象就是枚舉器,可以利用上面的三個方法進(jìn)行枚舉,非常安全。不過需要自己在繼承了接口的代碼中去寫實(shí)現(xiàn)過程。
一般的情況是:枚舉器是枚舉模式的一部分,通常被實(shí)現(xiàn)為枚舉類型(繼承IEnumerable)的一個嵌套類(繼承IEnumerator)。嵌套類的好處就是可以訪問外部類的私有成員,不破壞封裝的原則。
下面我們自己來定義一個枚舉類型,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public class SimpleCollection :IEnumerable { //定義一個數(shù)組的字段 private object [] array; //定義一個構(gòu)造函數(shù) public SimpleCollection( object []items) { array = items; } //實(shí)現(xiàn)IEnumerable接口的GetNumerator方法 該方法返回一個繼承IEnumerator接口的類的實(shí)例 public IEnumerator GetEnumerator() { return new Enumerator(array); } //定義一個嵌套類來繼承IEnumerator的接口 public class Enumerator : IEnumerator { //定義一個標(biāo)記字段 private int flag; //定義一個數(shù)組的字段 private object [] elements = null ; //定義一個構(gòu)造函數(shù) public Enumerator( object []items) { elements = items; flag = -1; //將標(biāo)記位初始化 //也可以采用下面的方法 //elements = new object[items.Length]; //Array.Copy(items, elements, items.Length);//此靜態(tài)方法用于將一個數(shù)組中的元素復(fù)制到另外一個數(shù)組 } //實(shí)現(xiàn)IEnumerator接口的Current屬性; 此屬性返回集合的當(dāng)前元素,是只讀的 public object Current { get { if (flag > elements.Length - 1) throw new InvalidOperationException( "枚舉已經(jīng)結(jié)束" ); else if (flag < 0) throw new InvalidOperationException( "枚舉尚未開始" ); else return elements[flag]; } } //實(shí)現(xiàn)IEnumerator接口的MoveNext方法 將枚舉移動到下一位 public bool MoveNext() { ++flag; if (flag > (elements.Length - 1)) return false ; else return true ; } //實(shí)現(xiàn)IEnumerator接口的Reset方法 使枚舉回到開頭 public void Reset() { flag = -1; } } |
下面來延時如何使用枚舉類型:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//下面來看枚舉類型的使用 SimpleCollection collection = new SimpleCollection( new object []{1,2,3,4,5}); //使用方法 //接口 變量名=繼承了該接口的類的實(shí)例 IEnumerator enumrator = collection.GetEnumerator(); while (enumrator.MoveNext()) { Console.WriteLine(enumrator.Current); } Console.ReadKey(); |
1
2
3
4
5
6
7
8
9
10
11
|
SimpleCollection simple = new SimpleCollection( new object [] { 1, 2, 3, 4, 5, 6 }); IEnumerator enumerator = simple.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); } //最重要的是,實(shí)現(xiàn)了那兩個接口,我們就可以對我們的集合使用foreach迭代了,看下面 foreach (var s in simple) { Console.WriteLine(s); } |
下面給出兩個接口的泛型實(shí)現(xiàn):
首先需要注意的是:
1.IEnumerable<T>接口繼承自IEnumerable 兩者具有相同接口,所以必須實(shí)現(xiàn)泛型和非泛型版本的GetEumerator方法
2.IEnumerator<T>接口繼承自IEnumerator和IDisposable 需要多實(shí)現(xiàn)泛型和非泛型版本的Current屬性和IDisposable接口的Dispose方法。
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
////下面創(chuàng)建一個可枚舉的泛類型 //首先該類型必須要繼承IEnumerable<T>接口 //因?yàn)镮Enumerable<T>接口繼承IEnumerable接口 所以必須同時實(shí)現(xiàn)泛型和非泛型的GetEnumerator方法 public class SimpleCollection<T> : IEnumerable<T> { private T[] array; public SimpleCollection(T[] items) { array = items; } //實(shí)現(xiàn)IEnumerable<T>接口的GetNumerator方法 該方法返回一個繼承IEnumerator接口的類的實(shí)例 public IEnumerator<T> GetEnumerator() { return new Enumerator<T>(array); //這步需要重視 } //為了避免混淆 在此顯式實(shí)現(xiàn)非泛型的接口 IEnumerator IEnumerable.GetEnumerator() { return new Enumerator<T>(array); //這步需要重視 } //定義一個嵌套類來繼承IEnumerator<T>的接口 //IEnumerator<T>接口繼承自IDisposable和IEnumerator接口 //該接口的唯一成員是Current屬性 但是同時也要實(shí)現(xiàn)其非泛型版本!!! //另外還需要實(shí)現(xiàn)IDisposable的Dispose方法和IEnumerator的兩個方法 public class Enumerator<_T> : IEnumerator<_T> { private int flag; private _T[] elements = null ; public Enumerator(_T[] items) { elements = items; flag = -1; } //實(shí)現(xiàn)IEnumerator<T>接口的Current屬性; 此屬性返回集合的當(dāng)前元素,是只讀的 public _T Current { get { if (flag > elements.Length - 1) throw new InvalidOperationException( "枚舉已經(jīng)結(jié)束" ); else if (flag < 0) throw new InvalidOperationException( "枚舉尚未開始" ); else return elements[flag]; } } //為了避免混淆 顯示實(shí)現(xiàn)IEnumerator接口的Current屬性 object IEnumerator.Current { get { return Current; } //直接返回上面的泛型屬性 比較經(jīng)典 } //實(shí)現(xiàn)IDisposable接口的Dispose方法 支持確定性垃圾回收 將枚舉數(shù)的狀態(tài)設(shè)置為after 也就是把標(biāo)記位設(shè)為最大索引+1 public void Dispose() { flag = elements.Length + 1; } //實(shí)現(xiàn)IEnumerator接口的MoveNext方法 將枚舉移動到下一位 public bool MoveNext() { ++flag; if (flag > (elements.Length - 1)) return false ; else return true ; } //實(shí)現(xiàn)IEnumerator接口的Reset方法 使枚舉回到開頭 public void Reset() { flag = -1; } } |
怎么使用呢:
1
2
3
4
5
6
7
8
9
10
11
|
SimpleCollection< string > colletion = new SimpleCollection< string >( new string [] { "ranran" , "Huaran" }); IEnumerator< string > enumorator = colletion.GetEnumerator(); while (enumorator.MoveNext()) { Console.WriteLine(enumorator.Current); } foreach (var v in colletion) { Console.WriteLine(v); } Console.ReadKey(); |
還可以直接使用迭代器:
使用迭代器是另一種完全實(shí)現(xiàn)上面兩個接口的方案,這是最為簡便和可讀的方法
而且使用迭代器可以很方便和快捷的設(shè)置各種枚舉情況 如雙重的迭代 反向的迭代 臨時的集合和負(fù)責(zé)迭代等等 比上面的實(shí)現(xiàn)更為簡單
迭代的關(guān)鍵字是yield 需要依靠一個迭代器塊(注意是循環(huán)+yield return,或者 yiled break)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class MyCollection:IEnumerable { private object [] array; public MyCollection( object []items) { array = items; } public IEnumerator GetEnumerator() //實(shí)現(xiàn)都可以依靠編譯器去完成 { //foreach (object v in array) //{ // yield return v; //} //關(guān)鍵字是yield 并不是foreach 我們也可以按照下面這個方法進(jìn)行實(shí)現(xiàn) for ( int i=0;i<array.Length;i++) { yield return array[i]; } //當(dāng)然其它的while循環(huán)也可以。。 } } //實(shí)現(xiàn): MyCollection collection = new MyCollection( new object [] { 1, 2, 3 }); foreach (var v in collection) { Console.WriteLine(v); } |
可以自己設(shè)置迭代的情況:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class MyCollection2:IEnumerable { private object [] array; public MyCollection2( object []items) { array = items; } //可以在迭代器塊中設(shè)置迭代的實(shí)現(xiàn)情況 即具體迭代多少個元素 //比如我們只想迭代4個元素 public IEnumerator GetEnumerator() { int count = 0; //設(shè)計(jì)一個標(biāo)記位 foreach ( object item in array) { ++count; yield return item; if (count == 4) yield break ; //break關(guān)鍵字 退出迭代 實(shí)際上迭代在實(shí)現(xiàn)當(dāng)中就是一個循環(huán) 利用break跳出也合情合理 } } } ////// MyCollection2 collection2 = new MyCollection2( new object []{4,5,6,7,8}); //它就只會輸出4,5,6,7 foreach (var v in collection2) { Console.WriteLine(v); } |
雙重迭代:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/// <summary> /// 下面演示雙重迭代 即一次可以迭代兩個集合中的元素 /// </summary> public class MyColletion3:IEnumerable { private object [] List1; public string [] List2; public MyColletion3( object []items1, string []items2) { this .List1 = items1; this .List2 = items2; } //下面進(jìn)行雙重迭代 public IEnumerator GetEnumerator() { //關(guān)鍵代碼 for ( int index=0;index<(List1.Length>List2.Length?List2.Length:List1.Length);index++) { yield return List1[index]; yield return List2[index]; } } } //////// MyColletion3 collection3 = new MyColletion3( new object [] { 1, 2, 3, 5.5 }, new string [] { "RanRan" , "Chengdu" , "四川" }); foreach (var v in collection3) { Console.WriteLine(v); } //迭代結(jié)果是1 RanRan 2 Chengdu 3 四川 |
反向迭代:依靠Reverse屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
/// <summary> /// 下面演示反向迭代 說白了就是迭代是從后面開始的 反向迭代器是在Reverse屬性當(dāng)中實(shí)現(xiàn)的 /// </summary> public class MyColletion4:IEnumerable { private object [] items; public MyColletion4( object []temps) { this .items = temps; } //一般的正向迭代 public IEnumerator GetEnumerator() { for ( int index=0;index<items.Length;index++) { yield return items[index]; } } //實(shí)現(xiàn)反向迭代 public IEnumerable Reverse //注意返回IEnumerable對象 { get { for ( int index = items.Length - 1; index > -1; index--) { yield return items[index]; } } } } //// MyColletion4 collection4 = new MyColletion4( new object [] { 1, 2, 3, 4 }); foreach (var v in collection4) { Console.WriteLine(v); } //反向迭代 foreach (var v in collection4.Reverse) { Console.WriteLine(v); } //迭代結(jié)果是 4 3 2 1 |
當(dāng)然也有一個臨時集合,順便補(bǔ)充一下,迭代和枚舉實(shí)現(xiàn)的方案很多,一個返回IEnumerable的方法中加上迭代器塊也是一個迭代集合
具體看下面的代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//還有一種最為簡單的迭代 就是一個返回IEnumerable對象的方法 在這方法中寫上迭代器 //在此補(bǔ)充一個臨時集合 關(guān)鍵看代碼怎么寫(以枚舉當(dāng)前月份的日期為列子) public static IEnumerable GetMonthDate() { DateTime dt = DateTime.Now; int currentMonth = dt.Month; while (currentMonth==dt.Month) { string temp = currentMonth.ToString() + "/" + dt.Day.ToString(); dt = dt.AddDays(1); yield return temp; } } ///實(shí)現(xiàn) foreach (var v in GetMonthDate()) { Console.WriteLine(v); } |
這兒 我作為一個新手自己給自己總結(jié)一下可枚舉類型和接口的含義:
可枚舉類型(集合&數(shù)組等):
在實(shí)際開發(fā)當(dāng)中,可以自己去定義一些與集合差不多的類型,對該類型的元素的訪問,用一般的while,for循環(huán)比較不方便,我們需要自己去定義一個枚舉器。
枚舉類型(繼承IEnumerable接口):包括一個集合元素和一個枚舉器。
枚舉器是枚舉類型當(dāng)中的一個嵌套類(繼承了IEnumerator接口):具體實(shí)現(xiàn)見上。
/////// 這樣便可以讓自定義的可枚舉類型實(shí)現(xiàn)foreach迭代。
當(dāng)然也可以直接利用迭代來實(shí)現(xiàn)上面兩個接口。//////
接口:是一種標(biāo)準(zhǔn),它給出了一種約束和引導(dǎo),需要我們?nèi)懘a實(shí)現(xiàn)它。雖然看上去多次一舉,不過在后面對類的實(shí)例的使用中非常方便。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/Huaran1chendu/p/4838536.html