C#中的迭代器(非常详细)
迭代器是包含一个或者多个 yield 语句的方法、属性或者索引器。迭代器必须返回以下四个接口之一(否则编译器会产生相应错误):
我们可以一次使用多个 yield 语句,例如:
我们可以将 Foo 修改为如下示例:
但是可以在只带有 finally 语句块的 try 语句块中使用 yield 语句:
当显式使用枚举器时,一个陷阱是提前结束枚举而不销毁枚举器,从而绕过 finally 语句块的执行。我们可以将枚举器的使用显式包裹在 using 语句中来避免上述错误。
// Enumerable interfaces System.Collections.IEnumerable System.Collections.Generic.IEnumerable<T> // Enumerator interfaces System.Collections.IEnumerator System.Collections.Generic.IEnumerator<T>
我们可以一次使用多个 yield 语句,例如:
foreach (string s in Foo()) Console.WriteLine(s); // Prints "one", "Two", "Three" IEnumerable<string> Foo() { yield return "one"; yield return "Two"; yield return "Three"; }
yield break语句
return 语句在迭代器块中是非法的。如果希望提前退出迭代器块,应该使用 yield break 语句。我们可以将 Foo 修改为如下示例:
IEnumerable<string> Foo(bool breakEarly) { yield return "one"; yield return "Two"; if (breakEarly) yield break; yield return "Three"; }
迭代器和try/catch/finally语句块
yield return 语句不能出现在带有 catch 子句的 try 语句块中:IEnumerable<string> Foo() { try { yield return "one"; } // Illegal catch { ... } }yield return 语句也不能出现在 catch 或者 finally 语句块中。出现这些限制的原因是编译器必须将迭代器转换为带有 MoveNext、Current 和 Dispose 成员的普通类,而转换异常处理语句块会大大增加代码的复杂性。
但是可以在只带有 finally 语句块的 try 语句块中使用 yield 语句:
IEnumerable<string> Foo() { try { yield return "one"; } // OK finally { ... } }当枚举器到达序列末尾或被销毁时就可以执行 finally 语句块了。如果枚举提前结束,则 foreach 语句会隐式销毁枚举器,这是消费枚举器的安全且正确的做法。
当显式使用枚举器时,一个陷阱是提前结束枚举而不销毁枚举器,从而绕过 finally 语句块的执行。我们可以将枚举器的使用显式包裹在 using 语句中来避免上述错误。
string firstElement = null; var sequence = Foo(); using (var enumerator = sequence.GetEnumerator()) { if (enumerator.MoveNext()) firstElement = enumerator.Current; }