C#学习笔记(三)—–C#高级特性:枚举类型和迭代

来源:互联网 发布:苹果手机蓝牙软件 编辑:程序博客网 时间:2024/05/20 16:37

C#学习笔记(三)—–try语句和异常

枚举类型

  • enumerator是只读的、只能在序列的值上向前移动的游标。一个enumerator是一个实现了下列任一接口的对象:
    System.Collections.IEnumerator
    System.Collections.Generic.IEnumerator<T>
    技术上讲,任何一个实现了MoveNext方法和Current属性的对象都可以被看作是一个enumerator(这就叫做鸭子类型,duck type)。这个宽泛的概念是在C#1.0的时候被引进的,目的是不用装箱和拆箱就可以对值类型的元素进行枚举。但是在C#2.0中,由于泛型的引入,这个功能变得多余了(这个优点在泛化的概念出现后就不存在了,而且实际上C#2.0已经不支持了)。
  • foreach语句可以用来在可枚举的对象上执行迭代操作,一个可枚举的对象是一个序列的逻辑上的表现。它本身不是一个游标,但他自身会产生游标(S)。一个可枚举对象可以是:
    ①:实现了IEnumerable or IEnumerable<T>接口
    ②:具有一个GetEnumerator方法并且返回一个enumerator
    提示IEnumerator 和 IEnumerable 定义在 System.Collections命名空间中,IEnumerator<T> 和 IEnumerable<T> 定义在System.Collections.Generic命名空间中

  • 枚举模式如下所示:

class Enumerator // 通常实现了IEnumerator或IEnumerator<T>{public IteratorVariableType Current { get {...} }public bool MoveNext() {...}}class Enumerable // 通常实现了IEnumerable 或 IEnumerable<T>{public Enumerator GetEnumerator() {...}}
  • 下面是用高级方法即foreach语句实现对单词beer的迭代:
foreach (char c in "beer")Console.WriteLine (c);
  • 下面使用低级方法即不用foreach语句,实现对单词beer的迭代:
using (var enumerator = "beer".GetEnumerator())while (enumerator.MoveNext()){var element = enumerator.Current;Console.WriteLine (element);}

如果enumerator实现了IDisposable,那么foreach语句也实现了using语句的作用,可以隐式释放enumerator对象。

集合初始化方法

  • 可以通过一个简单的步骤创建和填充一个可枚举的对象:
using System.Collections.Generic;...List<int> list = new List<int> {1, 2, 3};

编译器将这部分翻译为下面的代码:

using System.Collections.Generic;...List<int> list = new List<int>();list.Add (1);list.Add (2);list.Add (3);

这要求可枚举的对象实现System.Collections.IEnumerable接口,并且有add方法以及适合的参数来被调用。

迭代器

  • 和foreach是可枚举对象的使用者相对应的是迭代器是可枚举对象的生产者。本例中我们使用迭代器返回斐波那契数列表(斐波那契数列的定义是每个数字是前两个数字之和):
using System;using System.Collections.Generic;class Test{static void Main(){foreach (int fib in Fibs(6))Console.Write (fib + " ");}static IEnumerable<int> Fibs (int fibCount){for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++){yield return prevFib;int newFib = prevFib+curFib;prevFib = curFib;curFib = newFib;}}}

鉴于return语句表达的是“这是你要求我从方法返回的值”的意思,yield return语句表达的是“这是你要求我从enumerator产生的下一个元素”,在每一个yield语句中,控制返回调用者(caller),但是被调用者(callee)的状态被保持以便调用者(caller)想要枚举下一个值的时候立刻继续执行。这个状态的生命周期被绑定到enumerator上,以便当调用者结束枚举时状态可以被及时释放。
提示:编译器将迭代方法转换为实现了IEnumerable<T> 与/或 IEnumerator<T>.的私有类,迭代器的逻辑在编译器写的枚举类中,被反转并连接MoveNEext方法和Current属性,这表明当调用迭代方法时,所做的只是实例化编译器写的类,编写的代码并不真正运行,编写的代码只有在开始遍历结果序列时才会真正运行,典型的如foreach语句。下面是Fibs方法在后台生成的类:

[CompilerGenerated]private sealed class <Fibs>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable{// Fieldsprivate int <>1__state;private int <>2__current;public int <>3__fibCount;private int <>l__initialThreadId;public int <curFib>5__3;public int <i>5__1;public int <newFib>5__4;public int <prevFib>5__2;public int fibCount;// Methods[DebuggerHidden]public <Fibs>d__0(int <>1__state){this.<>1__state = <>1__state;this.<>l__initialThreadId = Environment.CurrentManagedThreadId;}private bool MoveNext(){switch (this.<>1__state){case 0:this.<>1__state = -1;this.<i>5__1 = 0;this.<prevFib>5__2 = 1;this.<curFib>5__3 = 1;while (this.<i>5__1 < this.fibCount){this.<>2__current = this.<prevFib>5__2;this.<>1__state = 1;return true;Label_0057:this.<>1__state = -1;this.<newFib>5__4 = this.<prevFib>5__2 + this.<curFib>5__3;this.<prevFib>5__2 = this.<curFib>5__3;this.<curFib>5__3 = this.<newFib>5__4;this.<i>5__1++;}break;case 1:goto Label_0057;}return false;}[DebuggerHidden]IEnumerator<int> IEnumerable<int>.GetEnumerator(){Program.<Fibs>d__0 d__;if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)){this.<>1__state = 0;d__ = this;}else{d__ = new Program.<Fibs>d__0(0);}Page 1 of 2about:blank 2017-05-22d__.fibCount = this.<>3__fibCount;return d__;}[DebuggerHidden]IEnumerator IEnumerable.GetEnumerator() =>this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();[DebuggerHidden]void IEnumerator.Reset(){throw new NotSupportedException();}void IDisposable.Dispose(){}// Propertiesint IEnumerator<int>.Current =>this.<>2__current;object IEnumerator.Current =>this.<>2__current;}Collapse MethodsPage 2 of 2about:blank 2017-05-22

迭代器语义

  • 迭代器是包含一个或多个yield语句的方法、属性、或索引器。迭代器必须返回下列四个接口之一(否则,编译器会报错):
    //IEnumerable接口
    System.Collections.IEnumerable
    System.Collections.Generic.IEnumerable<T>
    //IEnumerator接口
    System.Collections.IEnumerator
    System.Collections.Generic.IEnumerator<T>
    返回enumerable和返回enumerator的接口具有不同语义,我们将在后续的笔记中做详细的介绍。
  • 允许使用多个yield语句:
class Test{static void Main(){foreach (string s in Foo())Console.WriteLine(s); // Prints "One","Two","Three"}static IEnumerable<string> Foo(){yield return "One";yield return "Two";yield return "Three";//使用多个yield语句}}
  • yield break语句:该语句表明迭代器不返回后面的元素而提前结束。我们可以把Foo修改成下面的示例:
static IEnumerable<string> Foo (bool breakEarly){yield return "One";yield return "Two";if (breakEarly)yield break;yield return "Three";}

在迭代器块中使用return语句是不合法的,应该用yield return来代替。

迭代器和try/catch/finally

  • yield语句不能出现在带catch块的try语句块中。
IEnumerable<string> Foo(){try { yield return "One"; } // 不合法catch { ... }}

yield return也不能出现在catch和finally语句块中,出现这些限制的原因是编译器必须将迭代器转换为带有state、current、movenext和dispose成员的普通类。而且转换普通类可能会大大增加代码的复杂性。
但是,可以在只带有finally块的try语句块中使用yield return:

IEnumerable<string> Foo(){try { yield return "One"; } // OKfinally { ... }}

当枚举器到达序列末尾或者调用dispose时候,finally块中代码就会执行了,如果你提前使用了break语句,foreach语句会隐式的中断枚举器。这是一种正确的使用枚举器的方法。当显式的使用一个枚举器时,有一个陷阱是需要注意的:在没有关闭它的情况下提前放弃了枚举器。这使得finally语句块失去了作用。你可以显式的通过使用using语句来规避这一风险。

string firstElement = null;var sequence = Foo();using (var enumerator = sequence.GetEnumerator())if (enumerator.MoveNext())firstElement = enumerator.Current;

组合序列

  • 迭代器具有高度可组合性:我们扩展示例,这次只输出斐波那契数列中的偶数(注意读中文版的同学这里的翻译又开始严重的抽抽了,我诅咒这帮王八蛋):
using System;using System.Collections.Generic;class Test{static void Main(){foreach (int fib in EvenNumbersOnly (Fibs(6)))Console.WriteLine (fib);}static IEnumerable<int> Fibs (int fibCount){for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++){yield return prevFib;int newFib = prevFib+curFib;prevFib = curFib;curFib = newFib;}}static IEnumerable<int> EvenNumbersOnly (IEnumerable<int> sequence){foreach (int x in sequence)if ((x % 2) == 0)yield return x;}}

每一个元素直到最后才会被计算–当通过MoveNext()进行操作的时候。下图显示了随时间变化的数据请求和输出:
这里写图片描述
提示:为什么我在这里放一张英文的原图来代替翻译后的图,因为中文版的翻译这个图上本来就是错的。我没办法,只有将原图找来了。还是那句话。我从来没见过一本书会被翻译的如此不负责任。多么好的一本书。彻底毁了。

迭代器模式中的可组合性在linq中是非常有用的,这将在后续的笔记中陆续进行讨论。

阅读全文
0 0
原创粉丝点击