列举 - Enumeration

迭代器是一个值序列(集合)上的一个只读且只向前移动的游标。迭代器要么实现了IEnumerator接口,要么实现了IEnumerator<T>接口。

从技术的角度看,如果一个对象有MoveNext方法以及Current属性,那么我们就可以将其看作一个迭代器。

我们可以使用foreach语句去迭代一个可列举对象。可迭代的对象其实就是一个序列的逻辑体现。可列举的对象不但自身就是一个游标,而且它还可以生成一个游标迭代自己。因此,可列举的对象有两个特性

  • 实现IEnumerator接口,或实现IEnumerator<T>接口
  • 有一个方法GetEnumerator,该方法返回一个迭代器

列举模式:

class Enumerator
{
public IteratorVariableType Current {get {...}}
public bool MoveNext() {...}
} class Enumerable
{
public Enumerator GetEnumerator() {...}
}

Enumeration pattern

为了更好的理解上面的概率和模式,我们来看下面的两个例子

foreach (char c in "CSharp")
Console.WriteLine(c);

Sample 1

using (var enumerator = "CSharp".GetEnumerator())
{
while (enumerator.MoveNext()) {
Console.WriteLine(enumerator.Current);
}
}

Sample 2

Sample1采取了foreach这样的高级方式去迭代字符串(因为字符串类实现了CharEnumerator);而Sample2则使用了底层的方式完成对字符串的迭代。 对于Sample我们使用了using语句,这是因为CharEnumerator实现了IDisposable接口,下面的代码显示了CharEnumrator的大部分代码(来自微软官方)

public sealed class CharEnumerator : IEnumerator, IDisposable
{
private String str;
private int index;
private char currentElement; internal CharEnumerator(String str)
{
this.str = str;
this.index = -;
} public bool MoveNext()
{
if (index < (str.Length - ))
{
index++;
currentElement = str[index];
return true;
}
else
index = str.Length;
return false; } public void Dispose()
{
if (str != null)
index = str.Length;
str = null;
} public char Current
{
get
{
return currentElement;
}
} public void Reset()
{
currentElement = (char);
index = -;
}
}

CharEnumerator

初始化集合

我们可使用一行语句实例一个可列举的对象。比如:IList<Int> list = new List<int>{1,2,3};编译时,编译器会自动翻译为:

IList<Int> list = new List<int>();
list.Add();
list.Add();
list.Add();

Translated Code

这是因为该列举对象实现了IEnumerable接口,而且还包含了Add方法。
为了验证此点,我们可以通过查看IL代码的方式来确认:

IL_0000:  nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`<int32>::.ctor()
IL_0006: stloc.1
IL_0007: ldloc.1
IL_0008: ldc.i4.1
IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`<int32>::Add(!)
IL_000e: nop
IL_000f: ldloc.1
IL_0010: ldc.i4.2
IL_0011: callvirt instance void class [mscorlib]System.Collections.Generic.List`<int32>::Add(!)
IL_0016: nop
IL_0017: ldloc.1
IL_0018: ldc.i4.3
IL_0019: callvirt instance void class [mscorlib]System.Collections.Generic.List`<int32>::Add(!)
IL_001e: nop
IL_001f: ldloc.1
IL_0020: stloc.0
IL_0021: call string [mscorlib]System.Console::ReadLine()

IL Code

迭代器 - Iterator

既然foreach可应用于列举,那么一个列举可以生成一个迭代器。很绕口很困惑是吧,我们先来看下面的例子:使用迭代器返回斐波纳契数列

static IEnumerable<int> Fibonacci(int number)
{
for(int i=, prevFib=, curFib=;i<number;i++)
{
yield return prevFib;
int newFib = prevFib + curFib;
prevFib = curFib;
curFib = newFib;
}
} // test
static void Main(string[] args)
{
foreach (int f in Fibonacci())
Console.WriteLine(f); Console.ReadLine();
}

Fibonacci

请注意,在上面的代码中,我们使用了yield return。那么它和return有什么区别呢?
return:从方法中返回一个值
yield return:从当前的迭代器中生成下一个元素。yield语句每执行一次,程序的控制权就退还给调用者,而被调用者的状态仍然保留,这就使得方法在调用者列举下一个元素的时候能继续执行。被调用者的状态的生命周期取决于列举,正因为如此,当调用者完成列举后,被调用者的状态得以释放。

迭代器语法

迭代器可以是包含了一个或多个yield语句的方法、属性、或所引器。迭代器必须返回下面四个类型之一:IEnumerable, IEnumerable<T>, IEnumerator, IEnumerator<T>

再继续下一步之前,我们看一下IEnumerable接口和IEnumerator的定义

public interface IEnumerator
{
bool MoveNext();
Object Current {get; }
void Reset();
} public interface IEnumerable
{
IEnumerator GetEnumerator();
}

IEnumerator & IEnumerable

迭代器与列举有不一样的语法,在于迭代器需要返回可列举的接口或者列举器接口。

创建序列

迭代器可以进一步用于创建迭代。为了证实这点,我们可以扩展我们斐波纳契数列例子

static IEnumerable<int> Fibonacci(int number)
{
for(int i=, prevFib=, curFib=;i<number;i++)
{
yield return prevFib;
int newFib = prevFib + curFib;
prevFib = curFib;
curFib = newFib;
}
} static IEnumerable<int> EvenNumbers(IEnumerable<int> sequence)
{
foreach (int x in sequence)
if (x % == )
yield return x;
} static void Main(string[] args)
{
foreach (int f in EvenNumbers(Fibonacci()))
Console.WriteLine(f); Console.ReadLine();
}

Composable Iterator

请注意,直到Fibonacci方法所产生的数列的MoveNext()方法被调用时(执行foreach循环,会隐式地调用IEnumerator的MoveNext方法),才会判断该元素是否为偶数。

迭代器可以进一步用于创建迭代大量应用于LINQ。

c#列举和迭代器的更多相关文章

  1. java之迭代器

    迭代这个名词对于熟悉Java的人来说绝对不陌生.我们常常使用JDK提供的迭代接口进行java collection的遍历: Iterator it = list.iterator();while(it ...

  2. 小学生之Java中迭代器实现的原理

    一. 引言 迭代这个名词对于熟悉Java的人来说绝对不陌生.我们常常使用JDK提供的迭代接口进行java collection的遍历: Iterator it = list.iterator();wh ...

  3. Java 迭代器综述

    一.摘要 迭代器模式是与集合共生共死的.一般来说.我们仅仅要实现一个容器,就须要同一时候提供这个容器的迭代器.使用迭代器的优点是:封装容器的内部实现细节,对于不同的集合,能够提供统一的遍历方式,简化c ...

  4. Java中迭代器实现的原理

    一. 引言 迭代这个名词对于熟悉Java的人来说绝对不陌生.我们常常使用JDK提供的迭代接口进行java collection的遍历: Iterator it = list.iterator();wh ...

  5. c++之迭代器失效

    1.首先从一到题目开始谈说起迭代器失效.有时我们很自然并且自信地 用下面方法删除vector元素: #include <iostream>#include <stdio.h># ...

  6. [C#.NET 拾遗补漏]07:迭代器和列举器

    大家好,这是 [C#.NET 拾遗补漏] 系列的第 07 篇文章. 在 C# 中,大多数方法都是通过 return 语句立即把程序的控制权交回给调用者,同时也会把方法内的本地资源释放掉.而包含 yie ...

  7. python基础之迭代器、装饰器、软件开发目录结构规范

    生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大 ...

  8. 【转】【C#】迭代器

    迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式.简单来说,迭代器模式使得你能够获取到序列中的所有元素而 ...

  9. Python_Day5_迭代器、装饰器、软件开发规范

    本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 1.列表生成式,迭代器&生成器 列表生成 >>> a = [i+1 ...

随机推荐

  1. python文件选择:tkFileDialog 基础

    tkFileDialog有两种形式: 一个是.askopenfilename(option=value, ...) 这个是"打开"对话框 另一个是:asksaveasfilenam ...

  2. TypeScript 0.9.1 发布,新增 typeof 关键字

    TypeScript 0.9.1 发布了,该版本提升了编译器和语言的性能,增加新的语言特性 typeof ,更好的 this 处理等.详细介绍请看发行说明. TypeScript 是微软新推出的一种语 ...

  3. http://www.cnbeta.com/articles/306769.htm

    事实上,很少有方法可以帮你做到.有些人可能会想到试着把Vim打造成C语言IDE的,比如c.vim:也有把Vim集成到Eclipse里的Eclim .但是我想要告诉你的是一个更加通用的,只用插件实现的方 ...

  4. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  5. 简单理解ECMAScript2015中的箭头函数新特性

    箭头函数(Arrow functions),是ECMAScript2015中新加的特性,它的产生,主要有以下两个原因:一是使得函数表达式(匿名函数)有更简洁的语法,二是它拥有词法作用域的this值,也 ...

  6. Nagios学习笔记四:基于NRPE监控远程Linux主机

    1.NRPE简介 Nagios监控远程主机的方法有多种,其方式包括SNMP.NRPE.SSH和NCSA等.这里介绍其通过NRPE监控远程Linux主机的方式. NRPE(Nagios Remote P ...

  7. 在Debian下安装LAMP

    准备工作: 1 sudo apt-get install build-essential 第一步:安装Apache 1 sudo apt-get install apache2 第二步:安装MySQL ...

  8. celery简单应用

    写作背景介绍 在celery简单入门中已经介绍了写作的背景,这篇文章主要是深入介绍celery的使用技巧.在实际的项目中我们需要明确前后台的分界线,因此我们的celery编写的时候就应该是分成前后台两 ...

  9. paip. java resin 远程 调试 java resin remote debug

    paip. java resin 远程 调试 java resin remote debug 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 ...

  10. Leetcode 8 String to Integer (atoi) 字符串处理

    题意:将字符串转化成数字. 前置有空格,同时有正负号,数字有可能会溢出,这里用long long解决(leetcode用的是g++编译器),这题还是很有难度的. class Solution { pu ...