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++程序设计之编程思想
代码块愈小,代码的功能就愈容易管理,代码的处理和移动就愈轻松. 任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员. 绝大多数情况下,函数应该放在它所使用的数据的所 ...
随机推荐
- 微信小程序 获取用户信息 encryptData解密 C#版本
最近学习小程序开发,需要对encryptData解密,获取用户信息,官方源码没有C#版本,网上的资料比较杂,有的使用还有问题,下面贴一下自己亲试可以使用的一个源码 1.code 换取 session_ ...
- Windows下安装MySQL(解压版本)
解压缩 将下载到的文件解压缩到自己喜欢的位置,例如我自己的位置是D:\Program Files\mysql-5.7.10-winx64 添加环境变量 右键计算机->属性->高级系统设置- ...
- C# WPF 调用FFMPEG实现“SORRY 为所欲为/王境泽”表情包GIF生成软件
C# WPF 调用FFMPEG实现“SORRY 为所欲为/王境泽”表情包GIF生成 1,调用ffmpeg将外挂字幕“嵌入”视频中,保存副本: 2,调用ffmpeg将副本视频导出为gif图片. 参考资料 ...
- BigTable读后笔记
BigTable读后笔记 GFS可能出现重复记录或者padding,Bigtable如何处理这种情况使得对外提供强一致性模型? ANS: Bigtable写入GFS的数据分为两种: 1)操作日志,当T ...
- 算法 Tricks(六)—— 判断一个数是否为完全平方数
int(sqrt(n)) * int(sqrt(n)) == n ? 1:0; matlab 下判断一个数是否能开方的判断是: floor(sqrt(m))^2 == m
- style的继承
第一种方式:瞄准控件的基类 如下例所示,继承ContentControl的控件,都可以使用这个Style <Window.Resources> <Style x:Key=" ...
- QT添加openssl的方法
Assuming Windows you can download its installation from Win32 OpenSSL Installation Project page. You ...
- 微信小程序把玩(十八)picker组件
原文:微信小程序把玩(十八)picker组件 picker选择器分为三种,普通选择器,时间选择器, 日期选择器 用mode属性区分,默认是普通选择器.测试时时间和日期点击无反应不知道是BUG还是啥!没 ...
- JavaScript API for Office Outlook Add-in - “一页纸文档“
上一篇文章 Office Add-in Model 为 Outlook Mail Add-in 提供的 JavaScript API 介绍 ,简单地在表格中列出了所有的 Object 定义,但是个人感 ...
- windows qt 使用c++ posix接口编写多线程程序(真神奇)good
一.多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序.一般情况下,两种类型的多任务处理:基于进程和基于线程.基于进程的多任务处理是程序的并发执行.基于线程的多任务处理 ...