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

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
{
// Fields
private 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 2
about:blank 2017-05-22
d__.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()
{
}
// Properties
int IEnumerator<int>.Current =>
this.<>2__current;
object IEnumerator.Current =>
this.<>2__current;
}
Collapse Methods
Page 2 of 2
about: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"; } // OK
finally { ... }
}

当枚举器到达序列末尾或者调用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中是非常有用的,这将在后续的笔记中陆续进行讨论。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值