C#枚举数和迭代器
大道至简,始终认为简洁是一门优秀的编程语言的一个必要条件。相对来说,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>等接口,在这个类中维护了一个拥有四个状态的状态机:
- Before:首次调用MoveNext的初始状态
- Running:调用MoveNext后进入该状态。在这个状态中,枚举数检测并设置下一项的位置。在遇到yield return、yield break或迭代器块结束时退出状态
- Suspended:状态机等待下次调用MoveNext的状态
- After:没有更多项可以枚举
如果状态机在Before或Suspended状态有一次MoveNext调用就进入Running状态。在Running状态中检测集合的下一项并设置位置。如果有更多项状态机会转入Suspended状态,如果没有更多项则转入并保持在After状态,如图所示:
3.总结
总而言之,其实是对迭代器设计模式的运用简化。既不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据,这是迭代器设计模式的思想。
C#枚举数和迭代器的更多相关文章
- C#-14 枚举器和迭代器
一 枚举器和可枚举类型 当我们为数组使用foreach语句时,这个语句为我们依次取出了数组中的每一个元素. var arrInt = new int[] { 11, 12, 13, 14 }; for ...
- C#的枚举数(Enumerator)和可枚举类型(Enumerable)
数组可以被foreach语句遍历数组中的元素,原因是数组可以按需提供一个叫做枚举数(enumerator)的对象.枚举数可以依次返回请求的数组的元素. 对于有枚举数的类型而言,必须有一个方法来获取它们 ...
- C#图解教程 第十八章 枚举器和迭代器
枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...
- C# 枚举器和迭代器
一.枚举器(enumerator)和可枚举类型(enumeration) 我们都知道foreach语句可以用来遍历数组中的元素,但你有没有想过为什么它可以被foreach处理呢? 这是因为数组可以按需 ...
- 【C#】IEnumrator的枚举数和IEnumerable接口
声明IEnumerator的枚举数 要创建非泛型接口的枚举数,必须声明实现IEnumerator接口的类,IEnumerator接口有如下特性: 1.她是System.Collections命名空间的 ...
- 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释
适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...
- 实现自定义集合的可枚举类型(IEnumerable)和枚举数(IEnumerator )
下面的代码示例演示如何实现自定义集合的 IEnumerable 和 IEnumerator 接口: using System; using System.Collections; using Syst ...
- 暴力枚举-数长方形(hdu5258)
数长方形 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- C#知识点-枚举器和迭代器
一.几个基本概念的理解 问题一:为什么数组可以使用foreach输出各元素 答:数组是可枚举类型,它实现了一个枚举器(enumerator)对象:枚举器知道各元素的次序并跟踪它们的位置,然后返回请求的 ...
随机推荐
- python基础课程_2学习笔记3:图形用户界面
图形用户界面 丰富的平台 写作Python GUI程序前,须要决定使用哪个GUI平台. 简单来说,平台是图形组件的一个特定集合.能够通过叫做GUI工具包的给定Python模块进行訪问. 工具包 描写叙 ...
- HDU 3315 My Brute(费用流)
职务地址:HDU 3315 这个题的思路全然是自己想出来的,自我感觉挺巧妙的. . .(大牛勿喷.. . )对大胆建图又多了一份信心. 详细思路是构造一个二分图,Si连源点.Xi连汇点,流量都是1,费 ...
- freemarker导出word带图片
导出word带图片 如果你需要在word中添加图片,那你就在第一步制作模板时,加入一张图片占位,然后打开xml文档,可以看到如下的一片base64编码后的代码: <w:binData w:nam ...
- Wake-On-LAN待机或休眠模式中唤醒
Wake-On-LAN简称WOL,是一种电源管理功能:如果存在网络活动,则允许设备将操作系统从待机或休眠模式中唤醒.许多主板厂商支持IBM提出的网络唤醒标准.该标准允许网络管理员远程打开PC机电源,以 ...
- windows phone 墓碑化(9)
原文:windows phone 墓碑化(9) 几个术语的理解: 名称 说明 ; App ap = (App)Application.Current; // 构造函数 ...
- python学习之print输出不换行
print的即时打印会导致换行,要使得print的输出不换行,可以在字符串或者变量后面加个逗号(“,”),如下: s = "A bird in the hand..." for c ...
- Windows Phone开发(14):数据模板
原文:Windows Phone开发(14):数据模板 数据模板,如果你仅仅听到这个名词,你一定很迷惑,什么来的?用来干什么的?不急,亲,今天,我们一起来探索一下吧. 用白话文说,数据模板就是用来规范 ...
- Android使用百度定位SDK方法及错误处理
下面事例是使用Android平台的部分代码.对于这个平台百度的开放人员已经写了完整的demo,把工程导入到eclipse中之后一般没有错误,如果报错的话,eclipse也会给出提示.一般可以通过将pr ...
- JDK源码学习系列02----AbstractStringBuilder
JDK源码学习系列02----AbstractStringBuilder 因为看StringBuffer 和 StringBuilder 的源码时发现两者都继承了AbstractStringBuil ...
- C# 通过豆瓣网络编程API获取图书信息
这篇文章主要是关于如何通过豆瓣API获取信息的书籍,起初,我看到了原来的想法的内容是"C# 网络编程之网页简单下载实现"中通过HttpWebResponse类下载源代码,再通过正則 ...