c# 自己实现可迭代的容器
在c#中我们经常使用到foreach语句来遍历容器,如数组,List,为什么使用foreach语句能够遍历一个这些容器呢,首先的一个前提是这些容器都实现了IEnumerable接口,通过IEnumerable接口的GetEnumerator方法获得实现IEnumerator接口的对象。IEnumerator接口对象定义了两个方法外加一个属性。分别为MoveNext方法和Reset方法,以及Current属性。
一、foreach背后的故事
下面我们通过一个简单的例子来探索foreach背后究竟发生了什么,进而我们自己实现一个简单的可迭代的容器。
namespace CustomEnumerateTest
{
class Program
{
static void Main(string[] args)
{
List<int> l = new List<int>();
l.Add(1);
l.Add(2);
l.Add(3);
foreach (var v in l)
{
Console.WriteLine(v);
}
}
}
}
这是一段很简单的代码,foreach究竟是如何来实现容器对象的遍历的,我们通过ILDasm工具来查看c#编译器生成的中间码。代码如下,只贴出部分中间码:
IL_0020: ldloc.0
IL_0021: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() //调用List的GetEnumerator方法获取GetEnumerator对象
IL_0026: stloc.2
.try
{
IL_0027: br.s IL_003a //跳转指令,跳转到IL_003a处执行
IL_0029: ldloca.s CS$5$0000 //获取Enumerator对象的地址,push到堆栈。
IL_002b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() //调用Enumerator对象的get_Current()方法。
IL_0030: stloc.1
IL_0031: nop
IL_0032: ldloc.1
IL_0033: call void [mscorlib]System.Console::WriteLine(int32)
IL_0038: nop
IL_0039: nop
IL_003a: ldloca.s CS$5$0000
IL_003c: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()//调用Enumerator对象的MoveNext()方法。
IL_0041: stloc.3
IL_0042: ldloc.3
IL_0043: brtrue.s IL_0029
IL_0045: leave.s IL_0056
} // end .try
finally
{
IL_0047: ldloca.s CS$5$0000
IL_0049: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_004f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0054: nop
IL_0055: endfinally
} // end handler
上面贴出的就是对应源码中的foreach块的代码 。从红色部分我们可以看到了。首先编译器在遇到foreach语句时,会先调用List的GetEnumerator方法获得Enumerator对象,其中Enumerator对象实现了IEnumerator接口,GetEnumerator方法是IEnumerable接口,List实现了该接口。其次,编译器分别调用Enumerator对象的MoveNext方法,和Current方法获取对象的当前元素,这里是int类型的元素,不断循环直到条件为假,中间码IL_0043指令处的条件判断用于判断是否结束循环。
既然我们看到了foreach背后的真实的调用,那么现在我们自己实现一个简单的可迭代的容器,以便加深我们对IEnumerale和IEnumerator接口的理解。
二、简单的可迭代容器实现(本代码只是为了说明问题,对于各种异常情况没有做相应处理。)
首先来看一个没有实现IEnumerable的容器。
public class SimpleList<TIn>
{
private static TIn[] _element;
private const int DefaultSize = ;
private int _currentIndex = -;
private int _allocSize;
private int _length;
public SimpleList()
{
_element = new TIn[DefaultSize];
_allocSize = DefaultSize; } public TIn this[int index] { get { return _element[index]; } set { _element[index] = value; } }
public void Add(TIn value)
{ _currentIndex++;
if (_currentIndex >= DefaultSize)
{
_allocSize = _allocSize * ;
TIn[] tmp = _element;
_element = new TIn[_allocSize];
tmp.CopyTo(_element, ); }
_element[_currentIndex] = value;
_length++;
}
public int Length { get { return _length; }} }
这个SimpleList没有实现IEnumerable接口,所以我们不能通过foreach来访问它,编译器会提示类型is not enumerable。但是我们实现了Indexer,所以可以通过for语句来访问。
下面给SimpleList增加IEnumerable接口的实现完整代码:
public class SimpleList<TIn> : IEnumerable<TIn>
{
private static TIn[] _element;
private const int DefaultSize = ;
private int _currentIndex = -;
private int _allocSize;
private int _length;
public SimpleList()
{
_element = new TIn[DefaultSize];
_allocSize = DefaultSize; } public TIn this[int index] { get { return _element[index]; } set { _element[index] = value; } }
public void Add(TIn value)
{ _currentIndex++;
if (_currentIndex >= DefaultSize)
{
_allocSize = _allocSize * ;
TIn[] tmp = _element;
_element = new TIn[_allocSize];
tmp.CopyTo(_element, ); }
_element[_currentIndex] = value;
_length++;
}
public int Length { get { return _length; }} public IEnumerator<TIn> GetEnumerator()
{
return new Enumerator(this);
} IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public struct Enumerator : IEnumerator<TIn>
{
private SimpleList<TIn> list;
private int curIndex;
private TIn current; internal Enumerator(SimpleList<TIn> l)
{
list = l;
curIndex = ;
current = default (TIn);
}
public void Dispose()
{ } public bool MoveNext()
{
if (curIndex < list.Length)
{
current = list[curIndex];
curIndex++;
return true;
}
return false;
} public void Reset()
{
curIndex = ;
current = default (TIn);
} public TIn Current { get { return current; }} object IEnumerator.Current
{
get
{
if (curIndex == || curIndex == list.Length + )
{
throw new ArgumentException("curIndex");
}
return Current;
}
}
}
}
现在我们可以通过foreach来遍历SimpleList容器对象了。我们分别通过for和foreach来遍历:
class Program
{
static void Main(string[] args)
{ SimpleList<int> sl = new SimpleList<int>();
sl.Add();
sl.Add();
sl.Add();
Console.WriteLine("for 遍历:");
for (int i = ; i < sl.Length; i++)
{
Console.WriteLine(sl[i]);
}
Console.WriteLine("for each 遍历:");
foreach (var v in sl)
{
Console.WriteLine(v); } }
}
程序运行结果:

三、总结
通过以上的介绍我们实现迭代器对象首先是需要实现IEnumerate接口,其次为了遍历该对象中的元素我们需要实现IEnumerator接口,IEnumerate接口是为了获得Enumerator对象,只有获得了Enumerator对象我们才可以遍历集合的元素,这也是IEnumerate和IEnumerator的区别。IEnumerate接口告诉外界,该对象是可迭代的,具体如何迭代,是Enumerator接口实现的事情,因此,外界可以不需要知道Enumerator的存在。
c# 自己实现可迭代的容器的更多相关文章
- 【Python】【容器 | 迭代对象 | 迭代器 | 生成器 | 生成器表达式 | 协程 | 期物 | 任务】
Python 的 asyncio 类似于 C++ 的 Boost.Asio. 所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. Asyn ...
- C++STL模板库序列容器之List容器
目录 一丶List容器的存储结构 二丶丶STL中list容器的使用. 一丶List容器的存储结构 list容器底层是链表结构来维护的.跟vector不一样. vector是数组维护的.拥有连续内存.所 ...
- Ruby Enumerator的各种迭代
Enumerator迭代 Mix-in Enumerator获得的迭代方法: each_cons: each_slice: each_with_index: with_index: each_with ...
- C++ Primer 学习笔记_34_STL实践与分析(8) --引言、pair类型、关联容器
STL实践与分析 --引言.pair类型.关联容器 引言: 关联容器与顺序容器的本质差别在于:关联容器通过键[key]来存储和读取元素,而顺序容器则通过元素在容器中的位置顺序的存取元素. ma ...
- java并发容器
同步容器将所有对容器状态的访问都串行化,以实现线程安全性.这种方式的缺点是严重降低并发性.Java 5.0提供了多种并发容器来改进同步容器的性能.如ConcurrentHashMap代替同步且基于散列 ...
- String池与iterator对集合的迭代
一.静态导入 1. 导入类的静态属性 import static java.lang.System.out; out.println("haha"); 2. ...
- day11-Python运维开发基础(迭代器与可迭代对象、高阶函数)
1. 迭代器与可迭代对象 # ### 迭代器 """ 迭代器: 能被next方法调用,并且不断返回下一个值的对象,是迭代器(对象) 特征:迭代器会生成惰性序列,它通过计算 ...
- Java核心技术点之多线程
学习Java的同学注意了!!! 学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:279558494 我们一起学Java! 本文主要从整体上介绍Java中的多线程技术, ...
- c++程序设计之编程思想
代码块愈小,代码的功能就愈容易管理,代码的处理和移动就愈轻松. 任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员. 绝大多数情况下,函数应该放在它所使用的数据的所 ...
随机推荐
- 扪心自问,强大的UI框架,给我们带来了什么?(作者因此写了一个GuiLite)
MFC, QT, MINIGUI, ANDROID, IOS,如果这些平台,你都使用过,在下真心佩服,也相信你对这篇文章最具有发言权,真心期待你的回复和建议. 这些著名的UI说出来都让人如雷贯耳,如果 ...
- C#中的String.Format介绍
关键字:C# string.format作者:txw1958原文:http://www.cnblogs.com/txw1958/archive/2012/11/15/csharp-string_for ...
- vc有关 directx组态,和dxsdk_extras(directshow)
2009-11-10 0:28 此文章:自己编写 转载于<汤姆&杰瑞> DirectShow 1 -- 下载与VC配置 1 DirectX SDK9 Directshow sd ...
- JPA示例项(J采纳PA的hibernate实现版本号)
(1).JPA介绍: JPA全名Java Persistence API ,Java坚持API这是Sun公司Java EE 5规范中提出的Java持久化接口. JPA吸取了眼下Java持久化技术的长处 ...
- if-then和if-then-else声明
1.使用if-then声明 结构化命令,主要类型为if-then声明.if-then例如,下面的语句格式: if command then commands fi 假设你在使用其它编程语言的if-th ...
- 读BeautifulSoup官方文档之与bs有关的对象和属性(1)
自从10号又是5天没更, 是, 我再一次断更... 原因是朋友在搞python, 老问我问题, 我python也是很久没碰了, 于是为了解决他的问题, 我只能重新开始研究python, 为了快速找回感 ...
- wpf 路由事件 代码片段
<?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http: ...
- 查看 Linux 发行版本的名称以及版本号
查看LINUX发行版的名称及其版本号的命令: lsb_release -a cat /etc/redhat-release(针对redhat,Fedora)
- wpf自定义带刻度的柱状图控件
效果图: 主要代码xaml: <UserControl x:Class="INSControls._01Conning.Steer.ConningSpeedBar" xmln ...
- Qt for windows消息循环、libqxt分析和wince快捷键处理
Qt for windows消息循环.libqxt分析和wince快捷键处理 利用Qt做windows图形界面开发和MFC相比,个人感觉还是比较简单好用的:首先利用Designer工具搞个ui文件:然 ...