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. 创建一个字符串 这部分介绍一下如何创建一个字 ...
随机推荐
- hdu 3987 Harry Potter and the Forbidden Forest 求割边最少的最小割
view code//hdu 3987 #include <iostream> #include <cstdio> #include <algorithm> #in ...
- php浮点型以及精度问题
浮点型(也叫浮点数 float,双精度数 double 或实数 real) 浮点数的形式表示: LNUM [0-9]+DNUM ([0-9]*[\.]{LNUM}) | ({LNUM}[\.][0-9 ...
- SQL Server with(nolock)详解
大家在写查询时,为了性能,往往会在表后面加一个nolock,或者是with(nolock),其目的就是查询是不锁定表,从而达到提高查询速度的目的. 什么是并发访问:同一时间有多个用户访问同一资源,并发 ...
- [资料分享]迅为iTOP4412开发板-SDIO WiFi移植文档
本文转自迅为:http://www.topeetboard.com 概述 近期需要把WiFi无线网络功能移植到iTOP-4412 开发平台,查阅了相关资料,经过一段时间的研究.调试,终于成功的将WiF ...
- Learning C Struct
为什么需要结构体类型? 一种语言本身往往会提供一些最基本的数据类型,比如数字型(int,bigint,float,double等),字符型,日期型,布尔型等.但现实世界中,我们面对的对象总是非常复常, ...
- Java zip and unzip demo
目录结构如下: import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import ...
- java基础疑难点总结之成员变量的继承,方法重载与重写的区别,多态与动态绑定
1.成员变量的继承 1.1要点 子类用extends关键字继承父类.子类中可以提供新的方法覆盖父类中的方法.子类中的方法不能直接访问父类中的私有域,子类可以用super关键字调用父类中的方法.在子类中 ...
- 只有 DBA 才能导入由其他 DBA 导出的文件
两句话搞定问题: grant dba to testuser ; 如果还不行,再执行: alter user testuser default role DBA:
- 介绍Git版本控制器的使用
Git 简介 Git 是什么?大家肯定会说不就是版本控制器嘛,是的Git是目前世界上最先进的分布式版本控制系统(没有之一). 1.那什么是版本控制器呢? 举个简单的例子,比如我们用Word写文章,那你 ...
- STL整理
sort 升序排列: iterator lower_bound( const key_type &key ): 返回一个迭代器,指向键值>= key的第一个元素. iterator up ...