迭代器可用于逐步迭代集合,例如列表和数组。

  迭代器方法或 get 访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。

通过 foreach 语句或 LINQ 查询从客户端代码中使用迭代器。

  在以下示例中,foreach 循环的首次迭代导致 SomeNumbers 迭代器方法继续执行,直至到达第一个 yield return 语句。 此迭代返回的值为 3,并保留当前在迭代器方法中的位置。 在循环的下次迭代中,迭代器方法的执行将从其暂停的位置继续,直至到达 yield return 语句后才会停止。 此迭代返回的值为 5,并再次保留当前在迭代器方法中的位置。 到达迭代器方法的结尾时,循环便已完成。

static void Main()
{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// 输出: 3 5 8
Console.ReadKey();
} public static System.Collections.IEnumerable SomeNumbers()
{
yield return ;
yield return ;
yield return ;
}

迭代器方法或 get 访问器的返回类型可以是 IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>

可以使用 yield break 语句来终止迭代。

对于本主题中除简单迭代器示例以外的所有示例,请为 System.Collections 和 System.Collections.Generic 命名空间加入 using 指令。

简单的迭代器
 下例包含一个位于 for 循环内的 yield return 语句。 在 Main 中,foreach 语句体的每次迭代都会创建一个对迭代器函数的调用,并将继续到下一个 yield return 语句。
static void Main()
{
foreach (int number in EvenSequence(, ))
{
Console.Write(number.ToString() + " ");
}
// 输出: 6 8 10 12 14 16 18
Console.ReadKey();
} public static System.Collections.Generic.IEnumerable<int> EvenSequence(int firstNumber, int lastNumber)
{
// 迭代集合中的偶数.
for (int number = firstNumber; number <= lastNumber; number++)
{
if (number % == )
{
yield return number;
}
}
}
创建集合类

在以下示例中,DaysOfTheWeek 类实现 IEnumerable 接口,此操作需要 GetEnumerator 方法。 编译器隐式调用 GetEnumerator 方法,此方法返回 IEnumerator

GetEnumerator 方法通过使用 yield return 语句每次返回 1 个字符串。

static void Main()
{
DaysOfTheWeek days = new DaysOfTheWeek(); foreach (string day in days)
{
Console.Write(day + " ");
}
// 输出: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey();
} public class DaysOfTheWeek : IEnumerable
{
private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; public IEnumerator GetEnumerator()
{
for (int index = ; index < days.Length; index++)
{
// 迭代每一天
yield return days[index];
}
}
}

下例创建了一个包含动物集合的 Zoo 类。

引用类实例 (theZoo) 的 foreach 语句隐式调用 GetEnumerator 方法。 引用 BirdsMammals 属性的 foreach 语句使用 AnimalsForType 命名迭代器方法。

 static void Main()
{
Zoo theZoo = new Zoo(); theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler"); foreach (string name in theZoo)
{
Console.Write(name + " ");
}
Console.WriteLine();
// 输出: Whale Rhinoceros Penguin Warbler foreach (string name in theZoo.Birds)
{
Console.Write(name + " ");
}
Console.WriteLine();
// 输出: Penguin Warbler foreach (string name in theZoo.Mammals)
{
Console.Write(name + " ");
}
Console.WriteLine();
// 输出: Whale Rhinoceros Console.ReadKey();
} public class Zoo : IEnumerable
{
// 私有成员
private List<Animal> animals = new List<Animal>(); // 公共方法
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
} public void AddBird(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
} public IEnumerator GetEnumerator()
{
foreach (Animal theAnimal in animals)
{
yield return theAnimal.Name;
}
} // 公共成员
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
} public IEnumerable Birds
{
get { return AnimalsForType(Animal.TypeEnum.Bird); }
} // 私有方法
private IEnumerable AnimalsForType(Animal.TypeEnum type)
{
foreach (Animal theAnimal in animals)
{
if (theAnimal.Type == type)
{
yield return theAnimal.Name;
}
}
} // 私有类
private class Animal
{
public enum TypeEnum { Bird, Mammal } public string Name { get; set; }
public TypeEnum Type { get; set; }
}
}
对泛型列表使用迭代器

在以下示例中,Stack<T> 泛型类实现 IEnumerable<T> 泛型接口。 Push 方法将值分配给类型为 T 的数组。 GetEnumerator 方法通过使用 yield return 语句返回数组值。

除了泛型 GetEnumerator 方法,还必须实现非泛型 GetEnumerator 方法。 这是因为从 IEnumerable 继承了 IEnumerable<T>。 非泛型实现遵从泛型实现的规则。

本示例使用命名迭代器来支持通过各种方法循环访问同一数据集合。 这些命名迭代器为 TopToBottomBottomToTop 属性,以及 TopN 方法。

BottomToTop 属性在 get 访问器中使用迭代器。

 static void Main()
{
Stack<int> theStack = new Stack<int>(); // 向堆栈中添加项
for (int number = ; number <= ; number++)
{
theStack.Push(number);
} // 从堆栈中检索项。
// 此处允许使用 foreach,因为 foreach 实现了 IEnumerable<int>
foreach (int number in theStack)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// 输出: 9 8 7 6 5 4 3 2 1 0 // 此处允许使用 foreach,因为 theStack.TopToBottom 属性返回了 IEnumerable(Of Integer).
foreach (int number in theStack.TopToBottom)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// 输出: 9 8 7 6 5 4 3 2 1 0 foreach (int number in theStack.BottomToTop)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// 输出: 0 1 2 3 4 5 6 7 8 9 foreach (int number in theStack.TopN())
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// 输出: 9 8 7 6 5 4 3 Console.ReadKey();
} public class Stack<T> : IEnumerable<T>
{
private T[] values = new T[];
private int top = ; public void Push(T t)
{
values[top] = t;
top++;
}
public T Pop()
{
top--;
return values[top];
} // 此方法实现了GetEnumerator()方法. 它允许在 foreach 语句中使用类的实例。
public IEnumerator<T> GetEnumerator()
{
for (int index = top - ; index >= ; index--)
{
yield return values[index];
}
} IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
} public IEnumerable<T> TopToBottom
{
get { return this; }
} public IEnumerable<T> BottomToTop
{
get
{
for (int index = ; index <= top - ; index++)
{
yield return values[index];
}
}
} public IEnumerable<T> TopN(int itemsFromTop)
{
// 如有必要,返回少于 itemsFromTop
int startIndex = itemsFromTop >= top ? : top - itemsFromTop; for (int index = top - ; index >= startIndex; index--)
{
yield return values[index];
}
} }
语法信息

迭代器可用作一种方法,或一个 get 访问器。 不能在事件、实例构造函数、静态构造函数或静态终结器中使用迭代器。

必须存在从 yield return 语句中的表达式类型到迭代器返回的 IEnumerable<T> 类型参数的隐式转换。

在 C# 中,迭代器方法不能有任何 inrefout 参数。

在 C# 中,“yield”不是保留字,只有在 returnbreak 关键字之前使用时才有特殊含义。

技术实现

即使将迭代器编写成方法,编译器也会将其转换为实际上是状态机的嵌套类。 只要客户端代码中的 foreach 循环继续,此类就会跟踪迭代器的位置。

若要查看编译器执行的操作,可使用 Ildasm.exe 工具查看为迭代器方法生成的 Microsoft 中间语言代码。

结构创建迭代器时,不必实现整个 IEnumerator 接口。 编译器检测到迭代器时,会自动生成 IEnumeratorIEnumerator<T> 接口的 CurrentMoveNextDispose 方法。

foreach 循环(或对 IEnumerator.MoveNext 的直接调用)的每次后续迭代中,下一个迭代器代码体都会在上一个 yield return 语句之后恢复。 然后继续下一个 yield return 语句,直至到达迭代器体的结尾,或直至遇到 yield break 语句。

迭代器不支持 IEnumerator.Reset 方法。 若要从头开始重新迭代,必须获取新的迭代器。 在迭代器方法返回的迭代器上调用 Reset 会引发 NotSupportedException

有关其他信息,请参阅 C# 语言规范

迭代器的使用

需要使用复杂代码填充列表序列时,使用迭代器可保持 foreach 循环的简单性。 需执行以下操作时,这可能很有用:

  • 在第一次 foreach 循环迭代之后,修改列表序列。

  • 避免在 foreach 循环的第一次迭代之前完全加载大型列表。 一个示例是用于加载一批表格行的分页提取。 另一个示例关于 EnumerateFiles 方法,该方法在 .NET Framework 中实现迭代器。

  • 在迭代器中封装生成列表。 使用迭代器方法,可生成该列表,然后在循环中产出每个结果。

C#2.0新增功能05 迭代器的更多相关文章

  1. C#3.0新增功能05 分部方法

    连载目录    [已更新最新开发文章,点击查看详细]    分部类或结构可以包含分部方法. 类的一个部分包含方法的签名. 可以在同一部分或另一个部分中定义可选实现. 如果未提供该实现,则会在编译时删除 ...

  2. C#2.0新增功能01 分布类与分部方法

    连载目录    [已更新最新开发文章,点击查看详细] 分部类型 拆分一个类.一个结构.一个接口或一个方法的定义到两个或更多的文件中, 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组 ...

  3. C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点

    C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点   第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它 ...

  4. C#2.0新增功能06 协变和逆变

    连载目录    [已更新最新开发文章,点击查看详细] 在 C# 中,协变和逆变能够实现数组类型.委托类型和泛型类型参数的隐式引用转换. 协变保留分配兼容性,逆变则与之相反. 以下代码演示分配兼容性.协 ...

  5. C#基础拾遗系列之二:C#7.0新增功能点

    第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它包括封装.继承和多态性.C#面向对象的行为包括: 统一的类型系统 ...

  6. C#7.0新增功能点

    原文地址:  https://www.cnblogs.com/runningsmallguo/p/8972678.html 第二部分:C#7.0新增的功能 (1)数字字面量的提升: C#7中的数字文字 ...

  7. 说说C# 8.0 新增功能Index和Range的^0是什么?

    前言 在<C# 8.0 中使用 Index 和 Range>这篇中有人提出^0是什么意思?处于好奇就去试了,结果抛出异常.查看官方文档说^0索引与 sequence[sequence.Le ...

  8. C#3.0新增功能09 LINQ 基础05 使用 LINQ 进行数据转换

    连载目录    [已更新最新开发文章,点击查看详细] 语言集成查询 (LINQ) 不只是检索数据. 它也是用于转换数据的强大工具. 通过使用 LINQ查询,可以使用源序列作为输入,并通过多种方式对其进 ...

  9. C#3.0新增功能10 表达式树 05 解释表达式

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树中的每个节点将是派生自 Expression 的类的对象. 该设计使得访问表达式树中的所有节点成为相对直接的递归操作. 常规策略是从根节点 ...

随机推荐

  1. WIFI Manager

    Vistumbler - wifi managerhttps://www.vistumbler.net/downloads.htmlhttps://github.com/RIEI

  2. 80%的岗位是没有太多能力上的要求的(少部分聪明的人开始觉醒,这部分一定是那些主动追求、主动学习的人;30岁现象能区分真正专业和不学无术的人)good

    不要沦陷程序员的30岁问题     热门> 就是学习能力和工作热情态度的问题. 我之前也跟作者一样思考过这个问题,答案是否定的. 在知识积累的行业,年纪越大,越吃香,比如金融,医学,IT.就怕3 ...

  3. 《Windows via C/C++》学习笔记 —— 设备I/O之“同步的设备I/O”(系列文章)

    前面曾经讲过,设备I/O的方式有两种:同步和异步.本篇介绍一下同步设备I/O.主要涉及到两个函数:ReadFile和WriteFile. 不要被这两个函数的名称迷惑,不仅可以将这两个作用于文件,也可以 ...

  4. QObject提供了QMetaObject元类信息(相当于RTTI和反射),信号与连接,父子关系,调试信息,属性,事件,继承关系,窗口类型,线程属性,时间器,对象名称,国际化

    元类信息(相当于RTTI和反射),信号与连接,父子关系,调试信息,属性,事件,继承关系,窗口类型,线程属性,时间器,对象名称,国际化其中元类又提供了:classInfo,className,构造函数, ...

  5. FMX有两种消息处理的实现方式,一种是用TMessageManager来实现自定义的消息,另外一种象TEdit中的实现,直接声明消息方法(firemonkey messaging)

    看FMX代码,发现有两种消息处理的实现方式,一种是用TMessageManager来实现自定义的消息,另外一种象TEdit中的实现,直接声明消息方法.   早前,看过文章说TMessageManage ...

  6. 创建服务消费者(Feign)

    概述 Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单.使用 Feign,只需要创建一个接口并注解.它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS ...

  7. Kafka笔记3

    向Kafka写入消息从创建一个ProducerRecord对象开始,ProducerRecord需要包含目标主题和要发送的内容,我们还可以指定键或分区,在发送ProducerRecord对象时,生产者 ...

  8. Binary classification - 聊聊评价指标的那些事儿【回忆篇】

    在解决分类问题的时候,可以选择的评价指标简直不要太多.但基本可以分成两2大类,我们今分别来说道说道 基于一个概率阈值判断在该阈值下预测的准确率 衡量模型整体表现(在各个阈值下)的评价指标 在说指标之前 ...

  9. 系统学习 Java IO ---- 目录,概览

    Java IO 类的系统教程,原创.主要参考自英文教程 Java IO Tutorial 和 Java Doc. http://tutorials.jenkov.com/java-io/index.h ...

  10. 一路编程 -- Gruntfile.js

    <一路编程> Steven Foote 第四章构建工具 中的 Gruntfile.js 文件的 JSHint 部分,如果按照书中所写,run  grunt 的命令的时候会出错. 此处附上完 ...