原:<div class="article_title">            <span class="ico ico_type_Original"></span>    <h1>        <span class="link_title"><a target=_blank href="http://blog.csdn.net/byondocean/article/details/6871881">        IEnumerable和IEnumerator 详解        </a></span>    </h1></div>
初学C#的时候,老是被IEnumerable、IEnumerator、ICollection等这样的接口弄的糊里糊涂,我觉得有必要切底的弄清楚IEnumerable和IEnumerator的本质。 下面我们先看IEnumerable和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结构进行运算。
[csharp] view plaincopy 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对象中的每一个子项比较方便:
[csharp] view plaincopy //这看起来好像是可行的
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,如下所示:
[csharp] view plaincopy 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结构中安全使用该类型了。 [csharp] view plaincopy //除此之外,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接口中的方法:
[csharp] view plaincopy 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;
} #region 实现接口 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;
} #endregion
}
static void Main(string[] args)
{
// ForeachTest f = new ForeachTest("This is a sample sentence.", new char[] { ' ', '-' });
ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence.");
foreach (string item in f)
{
System.Console.WriteLine(item);
}
Console.ReadKey();
}
}
} IEnumerable<T>接口 实现了IEnmerable<T>接口的集合,是强类型的。它为子对象的迭代提供类型更加安全的方式。
[csharp] view plaincopy 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;
}
} [csharp] view plaincopy 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实例属性

IEnumerable和IEnumerator 详解 分类: C# 2014-12-05 11:47 18人阅读 评论(0) 收藏的更多相关文章

  1. 深入N皇后问题的两个最高效算法的详解 分类: C/C++ 2014-11-08 17:22 117人阅读 评论(0) 收藏

    N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行.同一列.同一斜线上的皇后都会自动攻击). 一. 求解N皇后问题是算法中回溯法应用的一个经典案例 回溯算 ...

  2. Java 函数参数传递方式详解 分类: Java Game 2014-08-15 06:34 82人阅读 评论(0) 收藏

    转:http://zzproc.iteye.com/blog/1328591 在阅读本文之前,根据自己的经验和理解,大家可以先思考并选择一下Java函数的参数传递方式:  A. 是按值传递的?  B. ...

  3. Least Common Ancestors 分类: ACM TYPE 2014-10-19 11:24 84人阅读 评论(0) 收藏

    #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...

  4. 二分图匹配(KM算法)n^4 分类: ACM TYPE 2014-10-04 11:36 88人阅读 评论(0) 收藏

    #include <iostream> #include<cstring> #include<cstdio> #include<cmath> #incl ...

  5. max_flow(Edmond_Karp) 分类: ACM TYPE 2014-09-02 10:47 92人阅读 评论(0) 收藏

    #include <cstdio> #include <iostream> #include <cstring> #include<queue> usi ...

  6. Segment Tree with Lazy 分类: ACM TYPE 2014-08-29 11:28 134人阅读 评论(0) 收藏

    #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; stru ...

  7. 8大排序算法图文讲解 分类: Brush Mode 2014-08-18 11:49 78人阅读 评论(0) 收藏

    排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 常见的内部排序算法有:插入排序.希尔排序. ...

  8. C语言之void类型及void指针 分类: C/C++ 2015-07-13 11:24 8人阅读 评论(0) 收藏

    原文网址:http://www.cnblogs.com/pengyingh/articles/2407267.html 1.概述 许多初学者对C/C 语言中的void及void指针类型不甚理解,因此在 ...

  9. 指向函数的指针 分类: C/C++ 2015-07-13 11:03 14人阅读 评论(0) 收藏

    原文网址:http://www.cnblogs.com/zxl2431/archive/2011/03/25/1995285.html 讲的很清楚,备份记录. (一) 用函数指针变量调用函数 可以用指 ...

随机推荐

  1. 奇虎360的开源OpenResty Windows版本

    https://github.com/LomoX-Offical/nginx-openresty-windows

  2. bzoj 3569 DZY Loves Chinese II 随机算法 树上倍增

    题意:给你一个n个点m条边的图,有若干组询问,每次询问会选择图中的一些边删除,删除之后问此图是否联通?询问之间相互独立.此题强制在线. 思路:首先对于这张图随便求一颗生成树,对于每一条非树边,随机一个 ...

  3. 简单生成随机数id的方法

    近期项目中需要生成不同的id,之前都是使用UUID来实现的,现在需求是只要8位数的,也就是说用户量是有限的,暂时是不需要太多的,所以就简单的使用Set实现了这一功能. /** * 生成8位不重复随机i ...

  4. [sqlmap源码阅读] 数据库识别

    通过网页返回的数据库错误信息识别网站所有数据库类型,用到的正则表达式及支持识别的数据库类型,这些信息以xml文件的形式存在,使用 sax 解析xml.

  5. AGC036C GP 2

    由于近期集训做的一直都是校内题 然后好久都怎么写题了( 发篇博客证明我还活着 (其实也没人关心 好像并不是很难的一道计数 就是脑子总是缺一块导致会做不出来( 首先我们可以分析性质 1.$\sum A_ ...

  6. Intent.java分析

    代码位于frameworks/base/core/java/anroid/Content/Intent.java Intent是对要进行操作的一种抽象描述.用action抽象操作,用data(andr ...

  7. Dubbo学习-4-dubbo简单案例-1

    模拟一个需求,通过dubbo实现RPC调用: 这里用户服务模块的查询用户地址的功能,就是一个服务提供者,而订单服务模块的创建订单模块就是一个服务消费者: 1. 创建服务提供者的maven工程:user ...

  8. 最佳实践 | 数据库迁云解决方案选型 & 流程全解析

    Oracle是非常强大的综合数据库,但同时也存在一些劣势,比如由于采用集中式架构,无法很好地实现横向扩展,并且其稳定性依赖于硬件.出于架构升级.降低成本和云化等需求,越来越多的企业需要“去Oracle ...

  9. Linux内核设计与实现 总结笔记(第四章)进程调度

    进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间. 调度程序没有太复杂的原理,最大限度地利用处理器时间的原则是,只要有可以执行的进程,那么就总会有进程正在执行. 一.多任务 多任 ...

  10. ckeditor如何能实现直接粘贴把图片上传到服务器中?

    在之前在工作中遇到在富文本编辑器中粘贴图片不能展示的问题,于是各种网上扒拉,终于找到解决方案,在这里感谢一下知乎中众大神以及TheViper. 通过知乎提供的思路找到粘贴的原理,通过TheViper找 ...