大道至简,始终认为简洁是一门优秀的编程语言的一个必要条件。相对来说,C#是比较简洁的,也越来越简洁。在C#中,一个关键字或者语法糖在编译器层面为我们做了很多乏味的工作,可能实现的是一个设计模式,甚至是一个算法。例如:lock关键字让用对象获取互斥锁从而实现线程同步,本质上是通过Monitor类来实现的,显然简洁很多。本文要讲的枚举数和迭代器在.net集合类被广泛使用,当然遵循着简洁的设计思想。

1.枚举数

1.1foreach的本质

我们知道,实现了IEnumerable接口的类型对象是可foreach遍历的,那么本质是什么呢?原来,在IEnumerable接口中定义这样一个方法:IEnumerator GetEnumerator(),

IEnumerator接口定义如下:

public interface IEnumerator
{
// 摘要:
// 获取集合中的当前元素。
//
// 返回结果:
// 集合中的当前元素。
//
// 异常:
// System.InvalidOperationException:
// 枚举数定位在该集合的第一个元素之前或最后一个元素之后。
object Current { get; } // 摘要:
// 将枚举数推进到集合的下一个元素。
//
// 返回结果:
// 如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
//
// 异常:
// System.InvalidOperationException:
// 在创建了枚举数后集合被修改了。
bool MoveNext();
//
// 摘要:
// 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
//
// 异常:
// System.InvalidOperationException:
// 在创建了枚举数后集合被修改了。
void Reset();
}

通过GetEnumerator方法,IEnumerable接口类型对象可以按需获取一个枚举数对象,枚举数可依次返回请求的集合元素作为迭代变量。

1.2枚举数的几种形式

上面说到实现了IEnumerable接口的类型对象是可foreach遍历的,这是充分不必要条件,实际上实现了IEnumerator GetEnumerator()方法的类型对象都是可枚举的。这里一共有三种形式:

  • IEnumerator/IEnumerable非泛型接口形式
  • IEnumerator<T>/IEnumerable<T>泛型接口形式
  • 自实现形式

1.2.1IEnumerator/IEnumerable非泛型接口形式

我们来简单实现一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections; namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
EnumerableEx arr = new object[] { "jello", , 'M' };
foreach (var item in arr)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
} class EnumerableEx : IEnumerable
{
object[] arr; public static implicit operator EnumerableEx(object[] _arr)
{
EnumerableEx _enum = new EnumerableEx();
_enum.arr = new object[_arr.Length];
for (int i = ; i < _arr.Length; i++)
{
_enum.arr[i] = _arr[i];
}
return _enum;
} public IEnumerator GetEnumerator()
{
return new EnumeratorEx(arr);
}
} class EnumeratorEx : IEnumerator
{
private int _pos = -;//当前元素位置
private object[] _array;//要遍历的数组 //构造函数
public EnumeratorEx(object[] array)
{
_array = array;
}
//迭代变量
public object Current
{
get
{
if (_pos == - || _pos >= _array.Length)
throw new InvalidOperationException();
return _array[_pos];
}
}
//移位
public bool MoveNext()
{
if (_pos < _array.Length - )
{
_pos++;
return true;
}
else
return false;
}
//重置
public void Reset()
{
_pos = -;
}
} }

这里首先向IEnumerable提供了需遍历的数组(使用了隐式用户自定义转换),在foreach中首先会调用GetEnumerator方法,然后MoveNext移到下一个位置,Current即为迭代变量。

1.2.2IEnumerator<T>/IEnumerable<T>泛型接口形式

非泛型接口形式中迭代变量是Object类型(非类型安全),这无法避免装箱和拆箱,尤其是当元素个数很多的时候,性能会消耗很大,因此引入了泛型接口形式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace ConsoleApplication1
{
class Program1
{
public static void Main(string[] args)
{
EnumerableEx<int> arr = new int[] { , , };
foreach (var item in arr)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
} class EnumerableEx<T> : IEnumerable<T>
{
T[] arr; public static implicit operator EnumerableEx<T>(T[] _arr)
{
EnumerableEx<T> _enum = new EnumerableEx<T>();
_enum.arr = new T[_arr.Length];
for (int i = ; i < _arr.Length; i++)
{
_enum.arr[i] = _arr[i];
}
return _enum;
} public IEnumerator<T> GetEnumerator()
{
return new EnumeratorEx<T>(arr);
} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new EnumeratorEx<T>(arr);
}
} class EnumeratorEx<T> : IEnumerator<T>
{
private int _pos = -;//当前元素位置
private T[] _array;//要遍历的数组 public EnumeratorEx(T[] array)
{
_array = array;
} public T Current
{
get
{
if (_pos == - || _pos >= _array.Length)
throw new InvalidOperationException();
return _array[_pos];
}
} public void Dispose()
{
//可用于释放非托管资源
} object System.Collections.IEnumerator.Current
{
get
{
if (_pos == - || _pos >= _array.Length)
throw new InvalidOperationException();
return _array[_pos];
}
} public bool MoveNext()
{
if (_pos < _array.Length - )
{
_pos++;
return true;
}
else
return false;
} public void Reset()
{
_pos = -;
}
} }

和非泛型接口形式基本一样,IEnumerable<T>除了继承IEnumerable接口,还继承了IDisposable接口用来释放非托管资源。

1.2.3自实现形式

自实现形式不继承自上面的接口,自定义一个实现GetEnumerator()的类和一个实现Current和MoveNext的类,好处是更加灵活,缺点是通用性差。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections; namespace ConsoleApplication1
{
class Program2
{
public static void Main(string[] args)
{
MyEnumerable<int> arr = new int[] { , , };
foreach (var item in arr)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}
class MyEnumerable<T>
{
T[] arr; public static implicit operator MyEnumerable<T>(T[] _arr)
{
MyEnumerable<T> _enum = new MyEnumerable<T>();
_enum.arr = new T[_arr.Length];
for (int i = ; i < _arr.Length; i++)
{
_enum.arr[i] = _arr[i];
}
return _enum;
} public MyEnumerator<T> GetEnumerator()
{
return new MyEnumerator<T>(arr);
}
}
class MyEnumerator<T>
{
private int _pos = -;//当前元素位置
private T[] _array;//要遍历的数组 public MyEnumerator(T[] array)
{
_array = array;
} public T Current
{
get
{
if (_pos == - || _pos >= _array.Length)
throw new InvalidOperationException();
return _array[_pos];
}
} public bool MoveNext()
{
if (_pos < _array.Length - )
{
_pos++;
return true;
}
else
return false;
} public void Reset()
{
_pos = -;
}
}
}

需要注意的是:Reset方法并不是必须要实现的。

2.迭代器

2.1What's 迭代器

迭代器是在.net2.0中引入的一种结构,旨在更加简单地创建枚举数和可枚举类型。先来看一个简单例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace ConsoleApplication1
{
class Program3
{
public static void Main(string[] args)
{
Iteration iteration = new Iteration();
foreach (var item in iteration)
{
Console.WriteLine(item);
}
Console.ReadKey();
} }
class Iteration
{
public IEnumerator<int> GetNums()
{
for (int i = ; i < ; i++)
{
yield return i;
}
} public IEnumerator<int> GetEnumerator()
{
return GetNums();
}
}
}

上面是通过yield return来获取枚举数的,通过运行结果发现,循环体内的yield return并没有在第一次迭代中返回,而是每次访问迭代变量时都能获取一个新元素值。

由一个或多个yield语句组成的代码块称为迭代器块,它和普通的代码块不同,并不是依次执行的,仅当需要获取迭代变量值时执行一次。

上面举了一个使用迭代器来创建枚举数的例子,其实,使用迭代器还可以创建可枚举类型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace ConsoleApplication1
{
class Program3
{
public static void Main(string[] args)
{
Iteration iteration = new Iteration();
foreach (var item in iteration)
{
Console.WriteLine(item);
}
Console.ReadKey();
} }
class Iteration
{
public IEnumerable<int> GetNums()
{
for (int i = ; i < ; i++)
{
yield return i;
}
} public IEnumerator<int> GetEnumerator()
{
return GetNums().GetEnumerator();
}
}
}

上面的两段代码有两个地方的不同:一是GetNums方法返回类型不同;二是GetEnumerator方法实现的不同。

2.2迭代器本质

我们使用简单的yield return就可以创建枚举数或可枚举类型,那么在编译器层面究竟做了些什么呢?通过IL代码可以管中窥豹:

原来,编译器在遇到迭代器块时会生成一个嵌套类,这个类实现了IEnumerable<T>和IEnumerator<T>等接口,在这个类中维护了一个拥有四个状态的状态机:

  1. Before:首次调用MoveNext的初始状态
  2. Running:调用MoveNext后进入该状态。在这个状态中,枚举数检测并设置下一项的位置。在遇到yield return、yield break或迭代器块结束时退出状态
  3. Suspended:状态机等待下次调用MoveNext的状态
  4. After:没有更多项可以枚举

如果状态机在Before或Suspended状态有一次MoveNext调用就进入Running状态。在Running状态中检测集合的下一项并设置位置。如果有更多项状态机会转入Suspended状态,如果没有更多项则转入并保持在After状态,如图所示:

3.总结

总而言之,其实是对迭代器设计模式的运用简化。既不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据,这是迭代器设计模式的思想。

C#枚举数和迭代器的更多相关文章

  1. C#-14 枚举器和迭代器

    一 枚举器和可枚举类型 当我们为数组使用foreach语句时,这个语句为我们依次取出了数组中的每一个元素. var arrInt = new int[] { 11, 12, 13, 14 }; for ...

  2. C#的枚举数(Enumerator)和可枚举类型(Enumerable)

    数组可以被foreach语句遍历数组中的元素,原因是数组可以按需提供一个叫做枚举数(enumerator)的对象.枚举数可以依次返回请求的数组的元素. 对于有枚举数的类型而言,必须有一个方法来获取它们 ...

  3. C#图解教程 第十八章 枚举器和迭代器

    枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...

  4. C# 枚举器和迭代器

    一.枚举器(enumerator)和可枚举类型(enumeration) 我们都知道foreach语句可以用来遍历数组中的元素,但你有没有想过为什么它可以被foreach处理呢? 这是因为数组可以按需 ...

  5. 【C#】IEnumrator的枚举数和IEnumerable接口

    声明IEnumerator的枚举数 要创建非泛型接口的枚举数,必须声明实现IEnumerator接口的类,IEnumerator接口有如下特性: 1.她是System.Collections命名空间的 ...

  6. 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释

    适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...

  7. 实现自定义集合的可枚举类型(IEnumerable)和枚举数(IEnumerator )

    下面的代码示例演示如何实现自定义集合的 IEnumerable 和 IEnumerator 接口: using System; using System.Collections; using Syst ...

  8. 暴力枚举-数长方形(hdu5258)

    数长方形 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  9. C#知识点-枚举器和迭代器

    一.几个基本概念的理解 问题一:为什么数组可以使用foreach输出各元素 答:数组是可枚举类型,它实现了一个枚举器(enumerator)对象:枚举器知道各元素的次序并跟踪它们的位置,然后返回请求的 ...

随机推荐

  1. 写代码质量改善java计划151建议——导航开始

    2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,慘不忍睹是吧.确实 ...

  2. Knockout应用开发指南 第九章:高级应用举例

    原文:Knockout应用开发指南 第九章:高级应用举例 1   Contacts editor 这个例子和微软为演示jQuery Data Linking Proposal例子提供的例子一样的提供的 ...

  3. 重温delphi之控制台程序:Hello World!

    原文:重温delphi之控制台程序:Hello World! 这二天用c#开发ActiveX时,发现不管怎么弄,c#就是没办法生成ocx的纯正activeX控件,而且还要强迫用户安装巨大的.net f ...

  4. Redshift扩容及踩到的坑

    下午发现redshift集群已经没有什么空间了.删掉一些不须要的暂时表也仅仅降到86%左右,为了能放下这两天的数据必须扩容了 watermark/2/text/aHR0cDovL2Jsb2cuY3Nk ...

  5. 利用Javamail接收QQ邮箱和Gmail邮箱(转)

    求大神解答 Java代码: public class SendMailController { //@Autowired private JavaMailSenderImpl mailSender; ...

  6. Xamarin For Visual Studio 3.7.165 完整离线破解版

    原文 Xamarin For Visual Studio 3.7.165 完整离线破解版 Xamarin For Visual Studio就是原本的Xamarin For Android 以及 Xa ...

  7. android ksoap2调用.net Webservice 方法总结

    android  ksoap2调用.net Webservice 方法直接放到一个类里: package com.util; import org.ksoap2.SoapEnvelope; impor ...

  8. oracle 转 mysql 最新有效法(转)

    关键字:Oracle 转 MySQL . Oracle TO MySQL 没事试用了一下Navicat家族的新产品Navicat Premium,他集 Oracle.MySQL和PostgreSQL管 ...

  9. UVa 11408 - Count DePrimes

    题目:一个数的素因子的和假设也是素数就叫做DePrimes,统计给定区间内的DePrimes. 分析:数论.本题使用用一种素数的筛法,欧拉筛法,也加线性筛法. 这样的方法,每次删选分两种情况:1.素因 ...

  10. Cocos移植Android-Android.mk编译后的文件

    在以前的博客,我们使用的中年cocos工具C和C++源代码可以编译. 其实cocos工具读取<游戏project文件夹>\proj.android\jni\夹Android.mk文件,.A ...