C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)
前言
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关键字的用法。
C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)的更多相关文章
- 学习javascript基础知识系列第三节 - ()()用法
总目录:通过一段代码学习javascript基础知识系列 注意: 为了便于执行和演示,建议使用chrome浏览器,按F12,然后按Esc(或手动选择)打开console,在console进行执行和演示 ...
- C# 基础知识系列- 3 集合数组
简单的介绍一下集合,通俗来讲就是用来保管多个数据的方案.比如说我们是一个公司的仓库管理,公司有一堆货物需要管理,有同类的,有不同类的,总而言之就是很多.很乱.我们对照集合的概念对仓库进行管理的话,那么 ...
- C# 基础知识系列- 10 反射和泛型(二)
0. 前言 这篇文章延续<C# 基础知识系列- 5 反射和泛型>,继续介绍C#在反射所开发的功能和做的努力.上一篇文章大概介绍了一下泛型和反射的一些基本内容,主要是通过获取对象的类型,然后 ...
- C# 基础知识系列- 17 小工具优化
0. 前言 不知道有没有动手能力强的小伙伴照着上一篇的内容写过程序呢?如果有的话,应该会在使用的时候发现以下几个问题: 每次启动都需要经过漫长的时间去遍历磁盘里的文件目录 因为数据是用的字典保存的,所 ...
- 基础知识系列☞C#中→属性和字段的区别
"好吧...准备写个'基础知识系列',算是记录下吧,时时看看,更加加深记忆···" 其实本来准备叫"面试系列"... 字段.属性.你先知道的哪个概念? ***我 ...
- 基础知识系列☞Abstract和Virtual→及相关知识
转载地址→http://www.cnblogs.com/blsong/archive/2010/08/12/1798064.html 在C#的学习中,容易混淆virtual方法和abstract方法的 ...
- 学习javascript基础知识系列第二节 - this用法
通过一段代码学习javascript基础知识系列 第二节 - this用法 this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascr ...
- java基础解析系列(九)---String不可变性分析
java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...
- C# 基础知识系列- 9 字符串的更多用法(一)
0. 前言 在前面的文章里简单介绍了一下字符串的相关内容,并没有涉及到更多的相关内容,这一篇将尝试讲解一下在实际开发工作中会遇到的字符串的很多操作. 1. 创建一个字符串 这部分介绍一下如何创建一个字 ...
随机推荐
- Rotate Array
Rotate an array of n elements to the right by k steps. For example, with n = 7 and k = 3, the array ...
- MPP 架构数据库
Greenplum是一种基于postgresql的分布式数据库.其采用shared nothing架构(MPP),主机,操作系统,内存,存储都是自我控制的,不存在共享.也就是每个节点都是一个单独的数据 ...
- zookeeper适用场景:如何竞选Master及代码实现
问题导读:1.如何利用zookeeper保证集群Master可用性和唯一性?2.zookeeper竞选Master包含哪些过程?3.zookeeper竞选Master机制利用了zk哪些特性? 在zoo ...
- matlab生成HEX文件-任意信号 大于64K长度
HEX文件格式不赘述,写里直接放上代码.请批评改正. %%convert a signal data into hex file format % data format:16bit % signal ...
- 数据结构--栈 codevs 1107 等价表达式
codevs 1107 等价表达式 2005年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Descripti ...
- codeforces 487C C. Prefix Product Sequence(构造+数论)
题目链接: C. Prefix Product Sequence time limit per test 1 second memory limit per test 256 megabytes in ...
- SGU 174 Walls
这题用并查集来做,判断什么时候形成了环即判断什么时候加入的线段两个端点原先是属于同一集合的.对于一个点,有两个坐标x,y,不好做并查集操作,于是要用map来存储,即做成map<node,int& ...
- AC日记——信息传递 洛谷 P2661 (tarjan求环)
题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一 ...
- Unity 协程与线程
协程是不同步的 协程 不是 线程,协同程序是 不同步 的 一个线程在程序中和其他线程是异步运行的,在多处理器机器中一个线程可以同时与所有其他线程的实时运行其代码,这使得线程编程能够解决很复杂的事情,因 ...
- JMeter学习参数化User Defined Variables与User Parameters
偶然发现JMeter中有两个元件(User Defined Variables与User Parameters)很相近,刚开始时我也没注意,两者有什么不同.使用时却发现两者使用场景有些不同,现在小结一 ...