IEnumerator:概念详解
IEnumerable接口是非常的简单,只包含一个抽象的方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象。IEnumerator对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。那么让我们看看IEnumerator接口有定义了什么东西。看下图我们知道IEnumerator接口定义了一个Current属性,MoveNext和Reset两个方法,这是多么的简约。既然IEnumerator对象时一个访问器,那至少应该有一个Current属性,来获取当前集合中的项吧。
MoveNext方法只是将游标的内部位置向前移动(就是移到一下个元素而已),要想进行循环遍历,不向前移动一下怎么行呢?
详细讲解:
说到IEnumerable总是会和IEnumerator、foreach联系在一起。
C# 支持关键字foreach,允许我们遍历任何数组类型的内容:
//遍历数组的项
int[] myArrayOfInts = {10,20,30,40};
foreach(int i in my myArrayOfInts)
{
Console.WirteLine(i);
}
虽然看上去只有数组才可以使用这个结构,其实任何支持GetEnumerator()方法的类型都可以通过foreach结构进行运算。
- public class Garage
- {
- Car[] carArray = new Car[4]; //在Garage中定义一个Car类型的数组carArray,其实carArray在这里的本质是一个数组字段
- //启动时填充一些Car对象
- public Garage()
- {
- //为数组字段赋值
- carArray[0] = new Car("Rusty", 30);
- carArray[1] = new Car("Clunker", 50);
- carArray[2] = new Car("Zippy", 30);
- carArray[3] = new Car("Fred", 45);
- }
- }
理想情况下,与数据值数组一样,使用foreach构造迭代Garage对象中的每一个子项比较方便:
- //这看起来好像是可行的
- lass Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");
- Garage carLot = new Garage();
- //交出集合中的每一Car对象吗
- foreach (Car c in carLot)
- {
- Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);
- }
- Console.ReadLine();
- }
- }
让人沮丧的是,编译器通知我们Garage类没有实现名为GetEnumerator()的方法(显然用foreach遍历Garage对象是不可能的事情,因为Garage类没有实现GetEnumerator()方法,Garage对象就不可能返回一个IEnumerator对象,没有IEnumerator对象,就不可能调用方法MoveNext(),调用不了MoveNext,就不可能循环的了)。这个方法是有隐藏在System.collections命名空间中的IEnumerable接口定义的。(特别注意,其实我们循环遍历的都是对象而不是类,只是这个对象是一个集合对象)
支持这种行为的类或结构实际上是宣告它们向调用者公开所包含的子项:
//这个接口告知调方对象的子项可以枚举
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
可以看到,GetEnumerator方法返回对另一个接口System.Collections.IEnumerator的引用。这个接口提供了基础设施,调用方可以用来移动IEnumerable兼容容器包含的内部对象。
//这个接口允许调用方获取一个容器的子项
public interface IEnumerator
{
bool MoveNext(); //将游标的内部位置向前移动
object Current{get;} //获取当前的项(只读属性)
void Reset(); //将游标重置到第一个成员前面
}
所以,要想Garage类也可以使用foreach遍历其中的项,那我们就要修改Garage类型使之支持这些接口,可以手工实现每一个方法,不过这得花费不少功夫。虽然自己开发GetEnumerator()、MoveNext()、Current和Reset()也没有问题,但有一个更简单的办法。因为System.Array类型和其他许多类型(如List)已经实现了IEnumerable和IEnumerator接口,你可以简单委托请求到System.Array,如下所示:
- namespace MyCarIEnumerator
- {
- public class Garage:IEnumerable
- {
- Car[] carArray = new Car[4];
- //启动时填充一些Car对象
- public Garage()
- {
- carArray[0] = new Car("Rusty", 30);
- carArray[1] = new Car("Clunker", 50);
- carArray[2] = new Car("Zippy", 30);
- carArray[3] = new Car("Fred", 45);
- }
- public IEnumerator GetEnumerator()
- {
- return this.carArray.GetEnumerator();
- }
- }
- }
- //修改Garage类型之后,就可以在C#foreach结构中安全使用该类型了。
- //除此之外,GetEnumerator()被定义为公开的,对象用户可以与IEnumerator类型交互:
- namespace MyCarIEnumerator
- {
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");
- Garage carLot = new Garage();
- //交出集合中的每一Car对象吗
- foreach (Car c in carLot) //之所以遍历carLot,是因为carLot.GetEnumerator()返回的项时Car类型,这个十分重要
- {
- Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);
- }
- Console.WriteLine("GetEnumerator被定义为公开的,对象用户可以与IEnumerator类型交互,下面的结果与上面是一致的");
- //手动与IEnumerator协作
- IEnumerator i = carLot.GetEnumerator();
- while (i.MoveNext())
- {
- Car myCar = (Car)i.Current;
- Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed);
- }
- Console.ReadLine();
- }
- }
- }
下面我们来看看手工实现IEnumberable接口和IEnumerator接口中的方法:
- namespace ForeachTestCase
- {
- //继承IEnumerable接口,其实也可以不继承这个接口,只要类里面含有返回IEnumberator引用的GetEnumerator()方法即可
- class ForeachTest:IEnumerable
- {
- private string[] elements; //装载字符串的数组
- private int ctr = 0; //数组的下标计数器
- /// <summary>
- /// 初始化的字符串
- /// </summary>
- /// <param name="initialStrings"></param>
- ForeachTest(params string[] initialStrings)
- {
- //为字符串分配内存空间
- elements = new String[8];
- //复制传递给构造方法的字符串
- foreach (string s in initialStrings)
- {
- elements[ctr++] = s;
- }
- }
- /// <summary>
- /// 构造函数
- /// </summary>
- /// <param name="source">初始化的字符串</param>
- /// <param name="delimiters">分隔符,可以是一个或多个字符分隔</param>
- ForeachTest(string initialStrings, char[] delimiters)
- {
- elements = initialStrings.Split(delimiters);
- }
- //实现接口中得方法
- public IEnumerator GetEnumerator()
- {
- return new ForeachTestEnumerator(this);
- }
- private class ForeachTestEnumerator : IEnumerator
- {
- private int position = -1;
- private ForeachTest t;
- public ForeachTestEnumerator(ForeachTest t)
- {
- this.t = t;
- }
- public object Current
- {
- get
- {
- return t.elements[position];
- }
- }
- public bool MoveNext()
- {
- if (position < t.elements.Length - 1)
- {
- position++;
- return true;
- }
- else
- {
- return false;
- }
- }
- public void Reset()
- {
- position = -1;
- }
- }
- static void Main(string[] args)
- {
- ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence.");
- foreach (string item in f)
- {
- System.Console.WriteLine(item);
- }
- Console.ReadKey();
- }
- }
- }
IEnumerable<T>接口
实现了IEnmerable<T>接口的集合,是强类型的。它为子对象的迭代提供类型更加安全的方式。
- public class ListBoxTest:IEnumerable<String>
- {
- private string[] strings;
- private int ctr = 0;
- #region IEnumerable<string> 成员
- //可枚举的类可以返回枚举
- public IEnumerator<string> GetEnumerator()
- {
- foreach (string s in strings)
- {
- yield return s;
- }
- }
- #endregion
- #region IEnumerable 成员
- //显式实现接口
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- #endregion
- //用字符串初始化列表框
- public ListBoxTest(params string[] initialStrings)
- {
- //为字符串分配内存空间
- strings = new String[8];
- //复制传递给构造方法的字符串
- foreach (string s in initialStrings)
- {
- strings[ctr++] = s;
- }
- }
- //在列表框最后添加一个字符串
- public void Add(string theString)
- {
- strings[ctr] = theString;
- ctr++;
- }
- //允许数组式的访问
- public string this[int index]
- {
- get {
- if (index < 0 || index >= strings.Length)
- {
- //处理不良索引
- }
- return strings[index];
- }
- set {
- strings[index] = value;
- }
- }
- //发布拥有的字符串数
- public int GetNumEntries()
- {
- return ctr;
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- //创建一个新的列表框并初始化
- ListBoxTest lbt = new ListBoxTest("Hello", "World");
- //添加新的字符串
- lbt.Add("Who");
- lbt.Add("Is");
- lbt.Add("Douglas");
- lbt.Add("Adams");
- //测试访问
- string subst = "Universe";
- lbt[1] = subst;
- //访问所有的字符串
- foreach (string s in lbt)
- {
- Console.WriteLine("Value:{0}", s);
- }
- Console.ReadKey();
- }
- }
综上所述,一个类型是否支持foreach遍历,必须满足下面条件:
方案1:让这个类实现IEnumerable接口
方案2:这个类有一个public的GetEnumerator的实例方法,并且返回类型中有public 的bool MoveNext()实例方法和public的Current实例属性。
IEnumerator:概念详解的更多相关文章
- JWT基础概念详解
JWT基础概念详解 JWT介绍 之前我们文章讲过分布式session如何存储,其中就讲到过Token.JWT.首先,我们来回顾一下使用Token进行身份认证. 客户端发送登录请求到服务器 服务器在用户 ...
- java入门---对象和类&概念详解&实例
Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 这篇文章,我们主要来看下: 对象:对象是类的一个实例(对象不是找个女朋友),有状态 ...
- Android屏幕密度(Density)和分辨率概念详解
移动设备有大有小,那么如何适应不同屏幕呢,这给我们编程人员造成了很多困惑.我也是突然想到这些问题,然后去网上搜搜相关东西,整理如下. 首先,对下面这些长度单位必须了解. Android中的长度单位 ...
- Storm 学习之路(二)—— Storm核心概念详解
一.Storm核心概念 1.1 Topologies(拓扑) 一个完整的Storm流处理程序被称为Storm topology(拓扑).它是一个是由Spouts 和Bolts通过Stream连接起来的 ...
- Storm 系列(二)—— Storm 核心概念详解
一.Storm核心概念 1.1 Topologies(拓扑) 一个完整的 Storm 流处理程序被称为 Storm topology(拓扑).它是一个是由 Spouts 和 Bolts 通过 Stre ...
- 图像处理术语解释:灰度、色相、饱和度、亮度、明度、阿尔法通道、HSL、HSV、RGBA、ARGB和PRGBA以及Premultiplied Alpha(Alpha预乘)等基础概念详解
☞ ░ 前往老猿Python博文目录 ░ 一.引言 由于老猿以前没接触过图像处理,在阅读moviepy代码时,对类的有些处理方法代码看不懂是什么含义,为此花了4天时间查阅了大量资料,并加以自己的理解和 ...
- 1-Hyperledger Fabric概念详解
目录 一.Hyperledger Fabric概述 二.基本术语 1.共享账本ledger 2.通道Channel 3.组织Org 4.智能合约Chaincode 5.背书Endorse 6.各种节点 ...
- Spring概念详解
1.什么是 Spring ? Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2E ...
- Maven 专题(五):Maven核心概念详解(一)
**Maven 的核心程序中仅仅定义了抽象的生命周期,而具体的操作则是由 Maven 的插件来完成的.**可是 Maven 的插件并不包含在 Maven 的核心程序中,在首次使用时需要联网下载. 下载 ...
随机推荐
- search支持多种标签
织梦的搜索页面支持dede标签的方法一 打开文件:include/arc.searchview.class.php 找到: require_once(DEDEINC."/taglib/hot ...
- Linux就这个范儿 第16章 谁都可以从头再来--从头开始编译一套Linux系统 nsswitch.conf配置文件
Linux就这个范儿 第16章 谁都可以从头再来--从头开始编译一套Linux系统 nsswitch.conf配置文件 朋友们,今天我对你们说,在此时此刻,我们虽然遭受种种困难和挫折,我仍然有一个梦 ...
- Chrome控制台调试工具用法
下面我们来看看console里面具体提供了哪些方法可以供我们平时调试时使用. Console API 当打开 firebug (也包括 Chrome 等浏览器的自带调试工具),window 下面会注册 ...
- Redis一些基本的操作
代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; using Syst ...
- RouterOS 软路由配置固定IP上网+DHCP
实现要求: 局域网所有PC机自动获取IP地址,能相互访问并且能访问外网 环境要求: 一台PC机安装两张网卡 ( 使用常用的网卡芯片,例如Intel芯片.RTL瑞昱芯片等 ) 配置说明 1.外网IP地址 ...
- vmware vcenter appliance dhcp 改为 静态IP导致web service认证失败
参考 http://www.davidhill.co/2012/09/failed-to-connect-to-vmware-lookup-service/ Failed to connect to ...
- Java基础之在窗口中绘图——绘制曲线(CurveApplet 1)
Applet程序. 定义自由曲线的类有两个,其中一个定义二次曲线,另一个定义三次曲线.这些自由曲线是用一系列线段定义的参数化曲线.二次曲线段用方程定义,方程包含独立变量x的平方.三次曲线也用方程定义, ...
- PAT 解题报告 1010. Radix (25)
1010. Radix (25) Given a pair of positive integers, for example, 6 and 110, can this equation 6 = 11 ...
- ubuntu14.04安装eclipse
1.安装jdk7.x,参考上篇<Linux14.04安装JDK> 2下载eclipse.tar 3.sudo tar -zxvf eclipse-x-x-.tar.gz mv eclips ...
- Lintcode: Nth to Last Node in List
Find the nth to last element of a singly linked list. The minimum number of nodes in list is n. Exam ...