迭代器(C# 和 Visual Basic)
迭代器 可用于通过集合步骤 (如列表和数组。
迭代器方法或 get 访问器对集合的自定义迭代。 迭代器方法使用 Yield (Visual Basic) 或 将返回 (c#) 语句返回每个元素一个节点。 当 Yield 或 yield return 语句时,代码的当前位置确保。 在下一次迭代器函数时,执行从该位置进行重新启动。
您使用来自客户端代码中的迭代器使用 对于每个 for each…next (Visual Basic) 或 foreach (c#) 使用 LINQ 查询,语句或。
在下面的示例中,For Each 或 foreach 循环的第一个迭代在直到第一 Yield 的 SomeNumbers 迭代器方法会导致执行或 yield return 语句为止。 此迭代返回值为 3,以及迭代方法的当前位置保留。 在循环的下一个迭代,在迭代器方法的执行从延续它将会停止,再次停止的位置,并在到达 Yield 或 yield return 语句时。 此迭代返回值为 5,以及迭代方法的当前位置再次保存。 在迭代器方法结束时,循环完成。
Sub Main()
For Each number As Integer In SomeNumbers()
Console.Write(number & " ")
Next
' Output: 3 5 8
Console.ReadKey()
End Sub
Private Iterator Function SomeNumbers() As System.Collections.IEnumerable
Yield 3
Yield 5
Yield 8
End Function
static void Main()
{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// Output: 3 5 8
Console.ReadKey();
}
public static System.Collections.IEnumerable SomeNumbers()
{
yield return 3;
yield return 5;
yield return 8;
}
迭代器方法或 get 访问器的返回类型可以是 IEnumerable、IEnumerable、IEnumerator或 IEnumerator。
可以使用 Exit Function "或 Return 语句 (Visual Basic) 或 yield break 语句 (C#) 结束迭代。
Visual Basic 迭代器函数或 get 访问器声明中包括一个 迭代器 修饰符。
迭代器在 Visual Studio 2005的 C# 在 Visual Studio 2012的 Visual Basic 表示和表示。
主题内容
简单的迭代器
创建集合选件类
尝试在 Visual Basic 块
在 Visual Basic 中的匿名方法
使用泛型的迭代器列表
语法信息
技术实现
对迭代器的使用
简单的迭代器
下面的示例有在 for…next 的单个 Yield 或 yield return 语句 (Visual Basic) 或 为 (c#) 循环内。 在 Main,For Each 或 foreach 语句体的每个迭代创建对迭代器函数,执行下一 Yield 或 yield return 语句。
Sub Main()
For Each number As Integer In EvenSequence(5, 18)
Console.Write(number & " ")
Next
' Output: 6 8 10 12 14 16 18
Console.ReadKey()
End Sub
Private Iterator Function EvenSequence(
ByVal firstNumber As Integer, ByVal lastNumber As Integer) _
As System.Collections.Generic.IEnumerable(Of Integer)
' Yield even numbers in the range.
For number As Integer = firstNumber To lastNumber
If number Mod 2 = 0 Then
Yield number
End If
Next
End Function
static void Main()
{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(number.ToString() + " ");
}
// Output: 6 8 10 12 14 16 18
Console.ReadKey();
}
public static System.Collections.Generic.IEnumerable<int>
EvenSequence(int firstNumber, int lastNumber)
{
// Yield even numbers in the range.
for (int number = firstNumber; number <= lastNumber; number++)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
创建集合选件类
备注
对于主题的其余的示例,请包括 导入 语句 (Visual Basic) 或 使用 指令 (c#) System.Collections 和 System.Collections.Generic 命名空间。
在下面的示例中,DaysOfTheWeek 选件类实现 IEnumerable 接口,需要一个 GetEnumerator 方法。 编译器隐式调用 GetEnumerator 方法,该方法返回 IEnumerator。
使用 Yield 或 yield return 语句,GetEnumerator 方法返回每个字符串一个节点。 在 Visual Basic 代码,Iterator 修饰符在函数声明。
Sub Main()
Dim days As New DaysOfTheWeek()
For Each day As String In days
Console.Write(day & " ")
Next
' Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey()
End Sub
Private Class DaysOfTheWeek
Implements IEnumerable
Public days =
New String() {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
Public Iterator Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
' Yield each day of the week.
For i As Integer = 0 To days.Length - 1
Yield days(i)
Next
End Function
End Class
static void Main()
{
DaysOfTheWeek days = new DaysOfTheWeek();
foreach (string day in days)
{
Console.Write(day + " ");
}
// Output: 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 = 0; index < days.Length; index++)
{
// Yield each day of the week.
yield return days[index];
}
}
}
下面的示例创建包含动物的集合 Zoo 选件类。
隐式引用选件类实例的 For Each 或 foreach 语句 (theZoo) 调用 GetEnumerator 方法。 引用 Birds 和 Mammals 属性的 For Each 或 foreach 语句使用命名迭代器方法的 AnimalsForType。
Sub Main()
Dim theZoo As New Zoo()
theZoo.AddMammal("Whale")
theZoo.AddMammal("Rhinoceros")
theZoo.AddBird("Penguin")
theZoo.AddBird("Warbler")
For Each name As String In theZoo
Console.Write(name & " ")
Next
Console.WriteLine()
' Output: Whale Rhinoceros Penguin Warbler
For Each name As String In theZoo.Birds
Console.Write(name & " ")
Next
Console.WriteLine()
' Output: Penguin Warbler
For Each name As String In theZoo.Mammals
Console.Write(name & " ")
Next
Console.WriteLine()
' Output: Whale Rhinoceros
Console.ReadKey()
End Sub
Public Class Zoo
Implements IEnumerable
' Private members.
Private animals As New List(Of Animal)
' Public methods.
Public Sub AddMammal(ByVal name As String)
animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Mammal})
End Sub
Public Sub AddBird(ByVal name As String)
animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Bird})
End Sub
Public Iterator Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
For Each theAnimal As Animal In animals
Yield theAnimal.Name
Next
End Function
' Public members.
Public ReadOnly Property Mammals As IEnumerable
Get
Return AnimalsForType(Animal.TypeEnum.Mammal)
End Get
End Property
Public ReadOnly Property Birds As IEnumerable
Get
Return AnimalsForType(Animal.TypeEnum.Bird)
End Get
End Property
' Private methods.
Private Iterator Function AnimalsForType( _
ByVal type As Animal.TypeEnum) As IEnumerable
For Each theAnimal As Animal In animals
If (theAnimal.Type = type) Then
Yield theAnimal.Name
End If
Next
End Function
' Private class.
Private Class Animal
Public Enum TypeEnum
Bird
Mammal
End Enum
Public Property Name As String
Public Property Type As TypeEnum
End Class
End Class
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();
// Output: Whale Rhinoceros Penguin Warbler
foreach (string name in theZoo.Birds)
{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Penguin Warbler
foreach (string name in theZoo.Mammals)
{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros
Console.ReadKey();
}
public class Zoo : IEnumerable
{
// Private members.
private List<Animal> animals = new List<Animal>();
// Public methods.
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 members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}
public IEnumerable Birds
{
get { return AnimalsForType(Animal.TypeEnum.Bird); }
}
// Private methods.
private IEnumerable AnimalsForType(Animal.TypeEnum type)
{
foreach (Animal theAnimal in animals)
{
if (theAnimal.Type == type)
{
yield return theAnimal.Name;
}
}
}
// Private class.
private class Animal
{
public enum TypeEnum { Bird, Mammal }
public string Name { get; set; }
public TypeEnum Type { get; set; }
}
}
尝试在 Visual Basic 块
Visual Basic 允许在 Try 的一个 Yield 语句块 Try...Catch...Finally 语句 (Visual Basic)。 Try 块有一个 Yield 语句可以具有 Catch 块,并可以具有 Finally 块。
C# 说明 |
---|
C# 允许在 try 的一个 yield return 语句块 try-finally 语句。try 块有一个 yield return 语句不能有任何 catch 块。 |
下面 Visual Basic 示例包括 Try,Catch,并且,Finally 在迭代器功能块。 在 For Each 迭代完成之前,Finally 在迭代器功能块执行。
Sub Main()
For Each number As Integer In Test()
Console.WriteLine(number)
Next
Console.WriteLine("For Each is done.")
' Output:
' 3
' 4
' Something happened. Yields are done.
' Finally is called.
' For Each is done.
Console.ReadKey()
End Sub
Private Iterator Function Test() As IEnumerable(Of Integer)
Try
Yield 3
Yield 4
Throw New Exception("Something happened. Yields are done.")
Yield 5
Yield 6
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
Console.WriteLine("Finally is called.")
End Try
End Function
Yield 语句不能在 Catch 块或 Finally 块。
如果 For Each 主体 (而不是迭代器方法) 引发异常,Catch 在迭代器功能块,不会执行,但 Finally 在迭代器功能块中执行。 Catch 块在迭代器功能出现在迭代器函数内仅捕获的异常中。
在 Visual Basic 中的匿名方法
在 Visual Basic (,但不在 c# 中),匿名函数可以是迭代器函数。 下面的示例阐释了这一点。
Dim iterateSequence = Iterator Function() _
As IEnumerable(Of Integer)
Yield 1
Yield 2
End Function
For Each number As Integer In iterateSequence()
Console.Write(number & " ")
Next
' Output: 1 2
Console.ReadKey()
下面 Visual Basic 示例有验证参数的非迭代器方法。 方法返回描述集合元素匿名迭代的结果。
Sub Main()
For Each number As Integer In GetSequence(5, 10)
Console.Write(number & " ")
Next
' Output: 5 6 7 8 9 10
Console.ReadKey()
End Sub
Public Function GetSequence(ByVal low As Integer, ByVal high As Integer) _
As IEnumerable
' Validate the arguments.
If low < 1 Then
Throw New ArgumentException("low is too low")
End If
If high > 140 Then
Throw New ArgumentException("high is too high")
End If
' Return an anonymous iterator function.
Dim iterateSequence = Iterator Function() As IEnumerable
For index = low To high
Yield index
Next
End Function
Return iterateSequence()
End Function
如果验证是在迭代器函数内,该验证无法执行直到 For Each 主体的第一个迭代开始。
使用泛型的迭代器列表
在下面的示例中,Stack(Of T) 泛型选件类实现 IEnumerable 泛型接口。 Push 方法将值分配给数组类型 T。 使用 Yield 或 yield return 语句,GetEnumerator 方法返回数组值。
除了泛型 GetEnumerator 方法之外,还必须执行非泛型 GetEnumerator 方法。 这是因为,IEnumerable 从 IEnumerable继承。 一个非泛型实现延迟到型实现。
该示例使用名为迭代器传递同一数据集合支持多种方式重复。 命名迭代器是 TopToBottom 和 BottomToTop 属性和 TopN 方法。
BottomToTop 属性。get 访问器使用一个迭代。 在 Visual Basic 代码,特性声明包括 Iterator 关键字。
Sub Main()
Dim theStack As New Stack(Of Integer)
' Add items to the stack.
For number As Integer = 0 To 9
theStack.Push(number)
Next
' Retrieve items from the stack.
' For Each is allowed because theStack implements
' IEnumerable(Of Integer).
For Each number As Integer In theStack
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 9 8 7 6 5 4 3 2 1 0
' For Each is allowed, because theStack.TopToBottom
' returns IEnumerable(Of Integer).
For Each number As Integer In theStack.TopToBottom
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 9 8 7 6 5 4 3 2 1 0
For Each number As Integer In theStack.BottomToTop
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 0 1 2 3 4 5 6 7 8 9
For Each number As Integer In theStack.TopN(7)
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 9 8 7 6 5 4 3
Console.ReadKey()
End Sub
Public Class Stack(Of T)
Implements IEnumerable(Of T)
Private values As T() = New T(99) {}
Private top As Integer = 0
Public Sub Push(ByVal t As T)
values(top) = t
top = top + 1
End Sub
Public Function Pop() As T
top = top - 1
Return values(top)
End Function
' This function implements the GetEnumerator method. It allows
' an instance of the class to be used in a For Each statement.
Public Iterator Function GetEnumerator() As IEnumerator(Of T) _
Implements IEnumerable(Of T).GetEnumerator
For index As Integer = top - 1 To 0 Step -1
Yield values(index)
Next
End Function
Public Iterator Function GetEnumerator1() As IEnumerator _
Implements IEnumerable.GetEnumerator
Yield GetEnumerator()
End Function
Public ReadOnly Property TopToBottom() As IEnumerable(Of T)
Get
Return Me
End Get
End Property
Public ReadOnly Iterator Property BottomToTop As IEnumerable(Of T)
Get
For index As Integer = 0 To top - 1
Yield values(index)
Next
End Get
End Property
Public Iterator Function TopN(ByVal itemsFromTop As Integer) _
As IEnumerable(Of T)
' Return less than itemsFromTop if necessary.
Dim startIndex As Integer =
If(itemsFromTop >= top, 0, top - itemsFromTop)
For index As Integer = top - 1 To startIndex Step -1
Yield values(index)
Next
End Function
End Class
static void Main()
{
Stack<int> theStack = new Stack<int>();
// Add items to the stack.
for (int number = 0; number <= 9; number++)
{
theStack.Push(number);
}
// Retrieve items from the stack.
// foreach is allowed because theStack implements
// IEnumerable<int>.
foreach (int number in theStack)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
// foreach is allowed, because theStack.TopToBottom
// returns IEnumerable(Of Integer).
foreach (int number in theStack.TopToBottom)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
foreach (int number in theStack.BottomToTop)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 0 1 2 3 4 5 6 7 8 9
foreach (int number in theStack.TopN(7))
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3
Console.ReadKey();
}
public class Stack<T> : IEnumerable<T>
{
private T[] values = new T[100];
private int top = 0;
public void Push(T t)
{
values[top] = t;
top++;
}
public T Pop()
{
top--;
return values[top];
}
// This method implements the GetEnumerator method. It allows
// an instance of the class to be used in a foreach statement.
public IEnumerator<T> GetEnumerator()
{
for (int index = top - 1; index >= 0; 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 = 0; index <= top - 1; index++)
{
yield return values[index];
}
}
}
public IEnumerable<T> TopN(int itemsFromTop)
{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;
for (int index = top - 1; index >= startIndex; index--)
{
yield return values[index];
}
}
}
语法信息
迭代器可能发生作为方法或 get 访问器。 迭代器在事件、实例构造函数、静态构造函数或静态析构函数不能出现。
隐式转换必须从该表达式存在输入 Yield (Visual Basic) 或 yield return (c#) 语句添加到迭代器的返回类型。
在 Visual Basic,迭代器方法不能有任何 ByRef 参数。 在 c# 中,迭代器方法不能有任何 ref 或 out 参数。
在 Visual Basic,只有 + 当,用于 Iterator 方法或 get 访问器时,“生成”不是保留字并具有特殊含义。 在 c# 中,只有 + 当,它在 return 或 break 关键字前时,使用“生成”不是保留字并具有特殊含义。
技术实现
尽管您以方法的形式编写迭代器,但编译器会将其转换为一个实际上是状态机的嵌套类。 此选件类长记录迭代器的位置作为 For Each...Next 或在客户端代码的 foreach 循环继续。
若要查看哪些编译器执行,可以使用 Ildasm.exe 工具查看用于迭代器方法生成的 Microsoft 中间语言 (msil) 代码。
当您创建 类 或 结构的某个迭代,您不必实现整个 IEnumerator 接口。 当编译器检测到迭代器时,会自动生成 Current、MoveNext和 IEnumerator 或 IEnumerator 接口的 Dispose 方法。
在每个后续迭代 For Each…Next 或 foreach 循环 (或直接调用 IEnumerator.MoveNext),以前的 Yield 或 yield return 语句后的下一个迭代器代码正文继续。 然后继续下一 Yield 或 yield return 语句,直到迭代器体的末尾为止,或在 Exit Function "或 Return 语句 (Visual Basic) 或 yield break 语句 (c#) 遇到。
迭代器不支持 IEnumerator.Reset 方法。 在这里重复一下从开始,您必须获取新的迭代器。
有关其他信息,请参见 Visual Basic 语言规范 或 C# 语言规范。
对迭代器的使用
当您需要使用复杂代码以填充列表顺序时,迭代器使您能够维护 For Each 循环的简单。 当您要执行以下操作时,这非常有用:
在第一 For Each 循环迭代后修改列表顺序。
避免完全加载用在 For Each 循环的第一个迭代之前列表。 示例是加载批中调用的获取表行。 另一个示例是 EnumerateFiles 方法,在 .NET framework 中实现迭代器。
封装生成列表在迭代器。 在迭代器方法,可在循环可以创建列表然后为每个结果。
下面的 c# 博客提供有关使用的附加信息迭代器。