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++程序设计之编程思想
代码块愈小,代码的功能就愈容易管理,代码的处理和移动就愈轻松. 任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员. 绝大多数情况下,函数应该放在它所使用的数据的所 ...
随机推荐
- PHP设置时区,记录日志文件的方法
关键字:PHP 时区 时间 日志 <html> <body> <?php date_default_timezone_set('Asia/Hong_Kong'); //s ...
- WPF教程002 - 实现Step步骤条控件
原文:WPF教程002 - 实现Step步骤条控件 在网上看到这么一个效果,刚好在用WPF做控件,就想着用WPF来实现一下 1.实现原理 1.1.该控件分为2个模块,类似ComboBox控件分为Ste ...
- maven_默认新建项目jdk1.6_默认配置
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...
- linq to entity DistinctBy && DefaultIfEmpty
根据某属性去重 使用第三方库: https://github.com/morelinq/MoreLINQ Install-Package morelinq -Version 3.0.0 data.Di ...
- 使用 NodeJS + Express从GET/POST Request 取值
过去无论哪一种网站应用程式的开发语言,初学者教学中第一次会提到的起手式,八九不离十就是GET/POST Request 的取值.但是,在Node.js + Express 的世界中,仿佛人人是高手,天 ...
- WPF 用代码增加路由事件的方法
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...
- jquery 相对元素
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- MVVM讲解
一,MVVM理论知识 从上一篇文章中,我们已经知道,WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时,WPF会自动发出通 ...
- WPF4.0用tablet实现手写输入(更新XP SP3下也能手写输入方法)
原文:WPF4.0用tablet实现手写输入(更新XP SP3下也能手写输入方法) 由于项目需求一个手写输入的控件,纠结了2天,终于搞定了. 主要是由于本人的英语不过关,一直和ocr混淆在一起,研究了 ...
- jquery模拟飞秋
<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...