IEnumerable和IEnumerator 详解 分类: C# 2014-12-05 11:47 18人阅读 评论(0) 收藏
原:<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) 收藏的更多相关文章
- 深入N皇后问题的两个最高效算法的详解 分类: C/C++ 2014-11-08 17:22 117人阅读 评论(0) 收藏
N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行.同一列.同一斜线上的皇后都会自动攻击). 一. 求解N皇后问题是算法中回溯法应用的一个经典案例 回溯算 ...
- Java 函数参数传递方式详解 分类: Java Game 2014-08-15 06:34 82人阅读 评论(0) 收藏
转:http://zzproc.iteye.com/blog/1328591 在阅读本文之前,根据自己的经验和理解,大家可以先思考并选择一下Java函数的参数传递方式: A. 是按值传递的? B. ...
- Least Common Ancestors 分类: ACM TYPE 2014-10-19 11:24 84人阅读 评论(0) 收藏
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...
- 二分图匹配(KM算法)n^4 分类: ACM TYPE 2014-10-04 11:36 88人阅读 评论(0) 收藏
#include <iostream> #include<cstring> #include<cstdio> #include<cmath> #incl ...
- max_flow(Edmond_Karp) 分类: ACM TYPE 2014-09-02 10:47 92人阅读 评论(0) 收藏
#include <cstdio> #include <iostream> #include <cstring> #include<queue> usi ...
- 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 ...
- 8大排序算法图文讲解 分类: Brush Mode 2014-08-18 11:49 78人阅读 评论(0) 收藏
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 常见的内部排序算法有:插入排序.希尔排序. ...
- 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指针类型不甚理解,因此在 ...
- 指向函数的指针 分类: C/C++ 2015-07-13 11:03 14人阅读 评论(0) 收藏
原文网址:http://www.cnblogs.com/zxl2431/archive/2011/03/25/1995285.html 讲的很清楚,备份记录. (一) 用函数指针变量调用函数 可以用指 ...
随机推荐
- 【技巧】Windows 10 1809无法接收1903解决方法
这都7月份了,Windows10 1903都升级的有一个月了,然而我的1809的系统一直找不到1903的更新. 虽说1903会有bug,但还是想体验一把.周围同事都更新了,心里还是痒痒的. 于是每天都 ...
- less:运算
less中的运算 -任何数字.颜色或者变量都可以参加运算,运算应该被包裹在括号中. -例如:+-*. @width: 30px; .box { width: (20 + 5) * @width; } ...
- php内置函数分析之strtoupper()、strtolower()
strtoupper(): PHP_FUNCTION(strtoupper) { zend_string *str; ZEND_PARSE_PARAMETERS_START(, ) Z_PARAM_S ...
- php session之多级目录存储
当选择以文件形式保存session到服务器时,需要制定保存路径.用到php.ini中的session.save_path,其有三种配置写法: session.save_path = "N;/ ...
- CF1090J Two Prefixes
神仙题++ 还是在某校梁大讲的题qaq 我们考虑容斥 也就是本质不同字串=全部-重复的 我们只需要求重复的即可 考虑相同的s=ab 我们用长度最长的a作为代表串 如果存在一个a'b'且|a'|> ...
- 爬虫技术:从sougou网站访问微信公众号的过程
一:分析过程:fidder + chrome开发者工具 1:输入nba跳转的页面,每页显示10条相关公众号的信息 2:分析网站得到每条标题的详情页链接地址在: 3,请求上图中的url,会返回一段js代 ...
- Cobaltstrike系列教程(三)-beacon详解
0x000--前文 Cobaltstrike系列教程(一)-简介与安装 Cobaltstrike系列教程(二)-Listner与Payload生成 heatlevel 0x001-Beacon详解 1 ...
- POJ 1797 Heavy Transprotation ( 最短路变形 || 最小生成树 )
题意 : 找出 1 到 N 点的所有路径当中拥有最大承载量的一条路,输出这个最大承载量!而每一条路的最大承载量由拥有最大承载量的那一条边决定 分析 : 与 POJ 2253 相似且求的东西正好相反,属 ...
- 20180826(04)-Java序列化
Java序列化 Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据.有关对象的类型的信息和存储在对象中数据的类型 将序列化对象写入文件之后,可 ...
- 20180826(02)-Java集合框架
Java 集合框架 早在Java 2中之前,Java就提供了特设类.比如:Dictionary, Vector, Stack, 和Properties这些类用来存储和操作对象组. 虽然这些类都非常有用 ...