C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)
查看并使用两个接口
接下来我们先来看看两个接口的定义。
先来看一下IEnumerable接口,其实看过这个接口之后,发现它其实是非常的简单,只包含一个方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象,如下面截图所示:
这里的IEnumerator对象,其实就是另外一个接口,这个接口对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。那么让我们看看IEnumerator接口又定义了什么东西。
从上面我们知道IEnumerator接口定义了一个Current属性,MoveNext和Reset两个方法,这是多么的简约。既然IEnumerator对象是一个访问器。那至少应该有一个Current属性,来获取当前集合中的项吧。MoveNext方法只是将游标的内部位置向前移动(就是移到一下个元素而已),要想进行循环遍历,不向前移动一下怎么行呢?
通过注释也可以明确的发现他们的用处。
下面我们来看一个简单的例子:
static void Main(string[] args) { int[] iArr = { 1, 3, 4, 6, 7, 9 }; foreach (int i in iArr) { Console.WriteLine(i.ToString()); } Console.ReadLine(); }
F5来运行代码
结果有了,说明简单的数组是可以支持foreach循环的。
下面我们来自己来做一个小例子,先来定义实体类
/// <summary> /// 个人的实体类 /// </summary> public class Person { public string Name { get; set; } public int Age { get; set; } } /// <summary> /// 一群人的实体类 /// </summary> public class People { Person[] personList = new Person[4]; public People() { personList[0] = new Person() { Name = "aehyok", Age = 25 }; personList[1] = new Person() { Name = "Kris", Age = 22 }; personList[2] = new Person() { Name = "Leo", Age = 21 }; personList[3] = new Person() { Name = "Niki", Age = 23 }; } }
如上面代码所示,一个Person类是个人的实体类,然后People类是一群人的实体类,按照和上面数组类似的格式,下面我们进行调用
static void Main(string[] args) { ///直接对一群人实例对象进行foreach People people = new People(); foreach (Person p in people) { Console.WriteLine("Name:{0}\tAge{1}",p.Name,p.Age); } Console.ReadLine(); }
还没来得及编译,错误就来了
所以我们根据上面的讲解我们就让People类实现IEnumerable接口吧。现在先来修改People实体类。
/// <summary> /// 个人的实体类 /// </summary> public class Person { public string Name { get; set; } public int Age { get; set; } } /// <summary> /// 一群人的实体类 /// </summary> public class People:IEnumerable { Person[] personList = new Person[4]; public People() { personList[0] = new Person() { Name = "aehyok", Age = 25 }; personList[1] = new Person() { Name = "Kris", Age = 22 }; personList[2] = new Person() { Name = "Leo", Age = 21 }; personList[3] = new Person() { Name = "Niki", Age = 23 }; } public IEnumerator GetEnumerator() { return this.personList.GetEnumerator(); } }
继承实现接口,完成该方法之后,就可以在调用时用foreach了。
注意:其实这里完全不用继承该接口。直接对GetEnumerator()方法进行实现,然后返回IEnumerator即可。
这样还可以有另外一种调用方式
static void Main(string[] args) { People people = new People(); foreach (Person p in people) { Console.WriteLine("Name:{0}\tAge{1}",p.Name,p.Age); } Console.WriteLine(""); ///直接获取迭代器 IEnumerator i = people.GetEnumerator(); while (i.MoveNext()) { Person person = (Person)i.Current; Console.WriteLine("Name:{0}\tAge{1}", person.Name, person.Age); } Console.ReadLine(); }
调用结果
自定义两个接口并进行实现
上面我们是通过继承微软类库中的接口来实现的实体集合的foreach遍历。下面我们来演示一下完全通过自己创建接口来实现自己定义的实例集合的foreach遍历。首先我们来实现一个简单的迭代器。
第一步:定义一个接口IMyEnumerator,之后所有迭代器都要进行实现
/// <summary> /// 要求所有的迭代器全部实现该接口 /// </summary> interface IMyEnumerator { bool MoveNext(); object Current{get;}; }
第二步:再定义一个接口IMyEnumerable,所有集合要实现该接口
/// <summary> /// 要求所有的集合实现该接口 /// 这样一来,客户端就可以针对该接口编码 /// 而无须关注具体的实现 /// </summary> interface IMyEnumerable { IMyEnumerator GetEnumerator(); int Count{get;}; }
第三步:一个简单的集合类MyList,实现IMyEnumerable。
class MyList:IMyEnumerable { int[] items = {0,1,2,3,4,5,6,7,8,9}; IMyEnumerator myEnumerator; public int this[int i] { get { return items[i]; } set { this.items[i] = value; } } public int Count { get { return items.Length; } } public IMyEnumerator GetEnumerator() { if (myEnumerator == null) { myEnumerator = new MyEnumerator(this); } return myEnumerator; } }
第四步:其实集合中也需要进行使用实现了第一步的迭代器,所以在此就是要实现这个迭代器
public class MyEnumerator:IMyEnumerator { int index = 0; MyList myList; public MyEnumerator(MyList myList) { this.myList = myList; } public bool MoveNext() { if (index + 1 > =myList.Count) { index = 1; return false; } else { index++; return true; } } public object Current { get { return myList[index]; } } }
第五步:简单调用进行调试
static void Main(string[] args) { ///使用接口IMyEnumerable代替MyList IMyEnumerable list = new MyList(); ///得到迭代器,在循环中针对迭代器进行编码,而不是集合MyList IMyEnumerator enumerator = list.GetEnumerator(); for (int i = 0; i < list.Count; i++) { object current = enumerator.Current; Console.WriteLine(current.ToString()); enumerator.MoveNext(); } Console.WriteLine(""); ///重新创建一个新的对象 IMyEnumerable list1 = new MyList(); IMyEnumerator enumerator1 = list1.GetEnumerator(); while (enumerator1.MoveNext()) //因为此处闲下移了一位,所以从1开始 { object current = enumerator1.Current; Console.WriteLine(current.ToString()); } Console.ReadLine(); }
调用结果
其实我定义的两个接口使用的是IMyEnumerable和IMyEnumerator,这里你直接可以去掉My那么就是微软类库里面的接口了,我这里只是自定义罢了,然后我自己定义接口的方法属性,没有严格按照微软的接口进行定义,但是差不多,只需要进行简单的修正就可以进行调用。这里有一个版本的。
其实上面例子中的调用我们就可以使用foreach来调用了,那么现在我们来用foreach来调用看看。
static void Main(string[] args) { MyList list=new MyList(); foreach (object obj in list) { Console.WriteLine(obj.ToString()); } Console.ReadLine(); }
总结
通过上面我实现的几个简单的例子可以发现,一个类型支持foreach遍历的条件可以是:
1、第一个方案是:这个类实现IEnumerable接口
2、第二个方案是:这个类有一个public的GetEnumerator的实例方法(不用继承IEnumerable实现接口),并且返回类型中有public 的bool MoveNext()实例方法和public的Current实例属性。
实现了IEnmerable<T>接口的集合,是强类型的。它为子对象的迭代提供类型更加安全的方式。
自己实现了下,感觉还是懂了一些,虽然还没有彻底的搞明白,但最起码大概知道怎么回事了。有空再来看看yield关键字的用法。