这篇文章主要介绍了C#中可枚举类型,IEnumerable和IEnumerator接口j及其泛型实现和迭代器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
正文
C#中可枚举类型详解
枚举是迭代一个集合中的数据项的过程。
我们经常使用的大多数集合实际上都已经实现了枚举的接口IEnumerable和IEnumerator接口,这样才能使用foreach迭代,有些是含有某种抽象了枚举细节的接口:ArrayList类型有索引,BitArray有Get方法,哈希表和字典有键和值..........其实他们都已经实现了IEnumerable和IEnumerator接口。所以一切的集合和数组都可以用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); } |
下面讲解一下 自己来定义可枚举类型(简单说就是自己定义的 ,可以进行foreach迭代的集合):
因为枚举非常有好处,可以消除很多的错误,所以实现某种标准是有好处的。这种标准就是IEnumerable和IEnumerator接口,必须实现了它才能够使用foreach迭代,才能真正算是一个自己定义的,功能健全的集合。
我们自己建立的可枚举类型必须实现IEnumerable和IEnumerator接口(其实两者都有一个泛型实现)。
IEnumerable接口含有一个方法,该方法返回一个枚举器对象,枚举器对象实现了IEnumerator接口(实际上可以认为继承和实现了IEnumerator的接口的类的对象就是枚举器对象),可以用它来进行迭代。
下面是两个接口的定义(系统早已经定义好):
1
2
3
4
|
public interface IEnumerable { IEnumerator GetEnumerator(); } |
该接口只有一个GetEnumerator的方法,返回一个枚举器,用于枚举集合中的元素。
1
2
3
4
5
6
|
public interface IEnumerator { object Current { get ; }; //Current属性返回集合的当前元素 bool MoveNext(); //将枚举移动到下一位 void Reset(); //使枚举回到开头 } |
凡是继承和实现了上面这个接口的类对象就是枚举器,可以利用上面的三个方法进行枚举,非常安全。不过需要自己在继承了接口的代码中去写实现过程。
一般的情况是:枚举器是枚举模式的一部分,通常被实现为枚举类型(继承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 { //定义一个数组的字段 private object [] array; //定义一个构造函数 public SimpleCollection( object []items) { array = items; } //实现IEnumerable接口的GetNumerator方法 该方法返回一个继承IEnumerator接口的类的实例 public IEnumerator GetEnumerator() { return new Enumerator(array); } //定义一个嵌套类来继承IEnumerator的接口 public class Enumerator : IEnumerator { //定义一个标记字段 private int flag; //定义一个数组的字段 private object [] elements = null ; //定义一个构造函数 public Enumerator( object []items) { elements = items; flag = -1; //将标记位初始化 //也可以采用下面的方法 //elements = new object[items.Length]; //Array.Copy(items, elements, items.Length);//此静态方法用于将一个数组中的元素复制到另外一个数组 } //实现IEnumerator接口的Current属性; 此属性返回集合的当前元素,是只读的 public object Current { get { if (flag > elements.Length - 1) throw new InvalidOperationException( "枚举已经结束" ); else if (flag < 0) throw new InvalidOperationException( "枚举尚未开始" ); else return elements[flag]; } } //实现IEnumerator接口的MoveNext方法 将枚举移动到下一位 public bool MoveNext() { ++flag; if (flag > (elements.Length - 1)) return false ; else return true ; } //实现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}); //使用方法 //接口 变量名=继承了该接口的类的实例 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); } //最重要的是,实现了那两个接口,我们就可以对我们的集合使用foreach迭代了,看下面 foreach (var s in simple) { Console.WriteLine(s); } |
下面给出两个接口的泛型实现:
首先需要注意的是:
1.IEnumerable<T>接口继承自IEnumerable 两者具有相同接口,所以必须实现泛型和非泛型版本的GetEumerator方法
2.IEnumerator<T>接口继承自IEnumerator和IDisposable 需要多实现泛型和非泛型版本的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
|
////下面创建一个可枚举的泛类型 //首先该类型必须要继承IEnumerable<T>接口 //因为IEnumerable<T>接口继承IEnumerable接口 所以必须同时实现泛型和非泛型的GetEnumerator方法 public class SimpleCollection<T> : IEnumerable<T> { private T[] array; public SimpleCollection(T[] items) { array = items; } //实现IEnumerable<T>接口的GetNumerator方法 该方法返回一个继承IEnumerator接口的类的实例 public IEnumerator<T> GetEnumerator() { return new Enumerator<T>(array); //这步需要重视 } //为了避免混淆 在此显式实现非泛型的接口 IEnumerator IEnumerable.GetEnumerator() { return new Enumerator<T>(array); //这步需要重视 } //定义一个嵌套类来继承IEnumerator<T>的接口 //IEnumerator<T>接口继承自IDisposable和IEnumerator接口 //该接口的唯一成员是Current属性 但是同时也要实现其非泛型版本!!! //另外还需要实现IDisposable的Dispose方法和IEnumerator的两个方法 public class Enumerator<_T> : IEnumerator<_T> { private int flag; private _T[] elements = null ; public Enumerator(_T[] items) { elements = items; flag = -1; } //实现IEnumerator<T>接口的Current属性; 此属性返回集合的当前元素,是只读的 public _T Current { get { if (flag > elements.Length - 1) throw new InvalidOperationException( "枚举已经结束" ); else if (flag < 0) throw new InvalidOperationException( "枚举尚未开始" ); else return elements[flag]; } } //为了避免混淆 显示实现IEnumerator接口的Current属性 object IEnumerator.Current { get { return Current; } //直接返回上面的泛型属性 比较经典 } //实现IDisposable接口的Dispose方法 支持确定性垃圾回收 将枚举数的状态设置为after 也就是把标记位设为最大索引+1 public void Dispose() { flag = elements.Length + 1; } //实现IEnumerator接口的MoveNext方法 将枚举移动到下一位 public bool MoveNext() { ++flag; if (flag > (elements.Length - 1)) return false ; else return true ; } //实现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(); |
还可以直接使用迭代器:
使用迭代器是另一种完全实现上面两个接口的方案,这是最为简便和可读的方法
而且使用迭代器可以很方便和快捷的设置各种枚举情况 如双重的迭代 反向的迭代 临时的集合和负责迭代等等 比上面的实现更为简单
迭代的关键字是yield 需要依靠一个迭代器块(注意是循环+yield return,或者 yiled break)
发表评论