首页 > 学院 > 开发设计 > 正文

c#列举和迭代器

2019-11-17 03:20:06
字体:
来源:转载
供稿:网友

c#列举和迭代器

列举 - 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 = -1;    }    public bool MoveNext()    {        if (index < (str.Length - 1))        {            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)0;        index = -1;    }}
CharEnumerator

初始化集合

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

IList<Int> list = new List<int>();list.Add(1);list.Add(2);list.Add(3);
Translated Code

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

IL_0000:  nop  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<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`1<int32>::Add(!0)  IL_000e:  nop  IL_000f:  ldloc.1  IL_0010:  ldc.i4.2  IL_0011:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)  IL_0016:  nop  IL_0017:  ldloc.1  IL_0018:  ldc.i4.3  IL_0019:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)  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=0, prevFib=1, curFib=1;i<number;i++)    {        yield return prevFib;        int newFib = prevFib + curFib;        prevFib = curFib;        curFib = newFib;    }}// teststatic void Main(string[] args){    foreach (int f in Fibonacci(10))        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=0, prevFib=1, curFib=1;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 % 2 == 0)            yield return x;}static void Main(string[] args){    foreach (int f in EvenNumbers(Fibo
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表