Iterátory (C#)
Iterátor lze použít k krokování kolekcí, jako jsou seznamy a pole.
Metoda iterátoru get nebo přistupující objekt provádí vlastní iteraci kolekce. Metoda iterátoru používá yield return k vrácení každého prvku po jednom. Při dosažení yield return příkazu se aktuální umístění v kódu zapamatovat. Provádění se z tohoto umístění restartuje při příštím volání funkce iterátoru.
Iterátor použijete z klientského kódu pomocí příkazu foreach nebo pomocí dotazu LINQ.
V následujícím příkladu první iterace smyčky způsobí, že provádění pokračuje v metodě iterátoru, dokud není dosaženo foreach SomeNumbers prvního yield return příkazu. Tato iterace vrátí hodnotu 3 a aktuální umístění v metodě iterátoru je zachováno. Při další iteraci smyčky pokračuje provádění v metodě iterátoru od místa, kde ji opustila, a po dosažení příkazu se znovu yield return zastaví. Tato iterace vrátí hodnotu 5 a aktuální umístění v metodě iterátoru se znovu zachová. Smyčka se dokončí při dosažení konce metody iterátoru.
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;
}
Návratový typ metody iterátoru nebo get přistupující objekt může být IEnumerable , , nebo IEnumerable<T> IEnumerator IEnumerator<T> .
K ukončení yield break iterace můžete použít příkaz .
Poznámka
Pro všechny příklady v tomto tématu s výjimkou příkladu jednoduchého iterátoru zahrnovat direktivy using pro System.Collections System.Collections.Generic obory názvů a .
Jednoduchý iterátor
Následující příklad obsahuje jeden yield return příkaz, který je uvnitř smyčky for. V systému každá iterace těla příkazu vytvoří volání funkce Main foreach iterátoru, která pokračuje k dalšímu yield return příkazu.
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;
}
}
}
Vytvoření třídy kolekce
V následujícím příkladu DaysOfTheWeek třída implementuje IEnumerable rozhraní, které vyžaduje GetEnumerator metodu . Kompilátor implicitně volá GetEnumerator metodu , která vrací IEnumerator .
Metoda GetEnumerator vrací každý řetězec po jednom pomocí příkazu yield return .
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];
}
}
}
Následující příklad vytvoří Zoo třídu, která obsahuje kolekci zvířat.
Příkaz, foreach který odkazuje na instanci třídy ( ), theZoo implicitně volá GetEnumerator metodu . Příkazy foreach odkazující na vlastnosti Birds a používají Mammals AnimalsForType pojmenovanou metodu iterátoru.
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; }
}
}
Používání iterátorů v obecných seznamech
V následujícím příkladu Stack<T> obecná třída implementuje IEnumerable<T> obecné rozhraní. Metoda Push přiřadí hodnoty k poli typu T . Metoda GetEnumerator vrátí hodnoty pole pomocí příkazu yield return .
Kromě obecné metody GetEnumerator musí být implementována také neobecná GetEnumerator metoda. Je to proto, IEnumerable<T> že dědí z IEnumerable . Neobecná implementace odchýlá obecnou implementaci.
Příklad používá pojmenované iterátory pro podporu různých způsobů iterace prostřednictvím stejné kolekce dat. Tyto pojmenované iterátory jsou vlastnosti TopToBottom BottomToTop a a TopN metodu .
Vlastnost BottomToTop používá iterátor v get přistupující objektu.
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];
}
}
}
Informace o syntaxi
Iterátor může nastat jako metoda nebo get přístupový objekt. Iterátor nemůže nastat v události, konstruktoru instance, statickém konstruktoru nebo statické finalizační metodě.
Implicitní převod musí existovat z typu výrazu v yield return příkazu na argument typu pro IEnumerable<T> vrácený iterátorem.
V jazyce C# nemůže mít metoda iterátoru žádné in parametry ref , nebo out .
V jazyce C# není vyhrazené slovo a má zvláštní význam pouze v případě, že se yield používá před klíčovým return slovem nebo break .
Technická implementace
I když iterátor píšete jako metodu, kompilátor ho přeloží do vnořené třídy, která je ve skutečnosti stavový počítač. Tato třída sleduje pozici iterátoru, pokud smyčka foreach v kódu klienta pokračuje.
Pokud chcete zjistit, co kompilátor dělá, můžete pomocí nástroje Ildasm.exe zobrazit kód zprostředkujícího jazyka Microsoftu, který je vygenerován pro metodu iterátoru.
Při vytváření iterátoru pro třídu nebo strukturynemusíte implementovat celé IEnumerator rozhraní. Když kompilátor zjistí iterátor, automaticky vygeneruje Current metody , a rozhraní nebo MoveNext Dispose IEnumerator IEnumerator<T> .
Při každé další iteraci smyčky (nebo přímém volání metody ) se tělo kódu dalšího iterátoru obnoví foreach IEnumerator.MoveNext po předchozím yield return příkazu. Potom pokračuje k dalšímu příkazu, dokud se nedosáhl konce těla iterátoru nebo dokud není yield return yield break zjištěn příkaz .
Iterátory nepodporují IEnumerator.Reset metodu . Chcete-li získat od začátku, je nutné získat nový iterátor. Volání Reset u iterátoru vráceného metodou iterátoru vyvolá NotSupportedException .
Další informace najdete ve specifikaci jazyka C#.
Používání iterátorů
Iterátory umožňují zachovat jednoduchost smyčky v případě, že k naplnění pořadí seznamu potřebujete použít složitý foreach kód. To může být užitečné, když chcete provést následující akce:
Upravte pořadí seznamu po první
foreachiteraci smyčky.Před první iterací smyčky se vyhněte úplnému načtení velkého
foreachseznamu. Příkladem je stránkované načtení pro načtení dávky řádků tabulky. Dalším příkladem je EnumerateFiles metoda , která implementuje iterátory v rozhraní .NET.Zapouzdřte sestavení seznamu v iterátoru. V metodě iterátoru můžete vytvořit seznam a pak vytvořit výsledek ve smyčce.