Iterators (C#)

Een iterator kan worden gebruikt om verzamelingen zoals lijsten en matrices te doorlopen.

Een iteratormethode of get accessor voert een aangepaste iteratie uit op een verzameling. Een iterator-methode gebruikt de retourinstructie voor rendement om elk element één voor één te retourneren. Wanneer een yield return instructie is bereikt, wordt de huidige locatie in code onthouden. De uitvoering wordt opnieuw gestart vanaf die locatie wanneer de iterator-functie de volgende keer wordt aangeroepen.

U gebruikt een iterator uit clientcode met behulp van een foreach-instructie of met behulp van een LINQ-query.

In het volgende voorbeeld zorgt de eerste iteratie van de foreach lus ervoor dat de uitvoering wordt voortgezet in de SomeNumbers iterator-methode totdat de eerste yield return instructie is bereikt. Deze iteratie retourneert een waarde van 3 en de huidige locatie in de iterator-methode wordt bewaard. Bij de volgende iteratie van de lus gaat de uitvoering in de iterator-methode verder vanaf waar deze is gebleven, en stopt opnieuw wanneer er een yield return instructie wordt bereikt. Deze iteratie retourneert een waarde van 5 en de huidige locatie in de iterator-methode wordt opnieuw bewaard. De lus wordt voltooid wanneer het einde van de iteratormethode is bereikt.

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;
}

Het retourtype van een iteratormethode of get accessor kan IEnumerablezijn, IEnumerable<T>of IEnumeratorIEnumerator<T>.

U kunt een yield break instructie gebruiken om de iteratie te beëindigen.

Eenvoudige Iterator

Het volgende voorbeeld heeft één yield return instructie die zich in een for-lus bevindt. In Main, elke iteratie van de foreach instructietekst maakt een aanroep naar de iterator-functie, die naar de volgende yield return instructie gaat.

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;
        }
    }
}

Een verzamelingsklasse maken

In het volgende voorbeeld implementeert de DaysOfTheWeek klasse de IEnumerable interface, waarvoor een GetEnumerator methode is vereist. De compiler roept impliciet de GetEnumerator methode aan, die een IEnumerator.

De GetEnumerator methode retourneert elke tekenreeks één voor één met behulp van de yield return instructie.

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];
        }
    }
}

In het volgende voorbeeld wordt een Zoo klasse gemaakt die een verzameling dieren bevat.

De foreach instructie die verwijst naar het klasse-exemplaar (theZoo) roept de GetEnumerator methode impliciet aan. De foreach instructies die naar de Birds en Mammals eigenschappen verwijzen, gebruiken de AnimalsForType benoemde iterator-methode.

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; }
    }
}

Iterators gebruiken met een algemene lijst

In het volgende voorbeeld implementeert de Stack<T> algemene klasse de IEnumerable<T> algemene interface. De Push methode wijst waarden toe aan een matrix van het type T. De GetEnumerator methode retourneert de matrixwaarden met behulp van de yield return instructie.

Naast de algemene methode moet ook de niet-generieke GetEnumeratorGetEnumerator methode worden geïmplementeerd. Dit komt omdat IEnumerable<T> ze overnemen van IEnumerable. De niet-algemene implementatie wordt uitgesteld tot de algemene implementatie.

In het voorbeeld worden benoemde iterators gebruikt om verschillende manieren te ondersteunen om dezelfde verzameling gegevens te doorlopen. Deze benoemde iterators zijn de TopToBottom en BottomToTop eigenschappen en de TopN methode.

De BottomToTop eigenschap gebruikt een iterator in een get accessor.

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];
        }
    }

}

Syntaxisinformatie

Een iterator kan optreden als een methode of get accessor. Een iterator kan niet voorkomen in een gebeurtenis, instantieconstructor, statische constructor of statische finalizer.

Er moet een impliciete conversie bestaan van het expressietype in de yield return instructie naar het typeargument voor de IEnumerable<T> geretourneerde door de iterator.

In C# kan een iteratormethode geen in, refof out parameters hebben.

In C# yield is geen gereserveerd woord en heeft alleen speciale betekenis wanneer deze wordt gebruikt voor een return of break meer trefwoorden.

Technische implementatie

Hoewel u een iterator schrijft als een methode, vertaalt de compiler deze in een geneste klasse die in feite een statusmachine is. Deze klasse houdt de positie van de iterator bij zolang de foreach lus in de clientcode wordt voortgezet.

Als u wilt zien wat de compiler doet, kunt u het hulpprogramma Ildasm.exe gebruiken om de algemene tussentaalcode weer te geven die wordt gegenereerd voor een iterator-methode.

Wanneer u een iterator voor een klasse of struct maakt, hoeft u de hele IEnumerator interface niet te implementeren. Wanneer de compiler de iterator detecteert, worden de Current, MoveNexten Dispose methoden van de IEnumerator of IEnumerator<T> interface automatisch gegenereerd.

Bij elke opeenvolgende iteratie van de foreach lus (of de directe aanroep naar IEnumerator.MoveNext), wordt de volgende iterator-codetekst hervat na de vorige yield return instructie. Vervolgens gaat het verder met de volgende yield return instructie totdat het einde van de iterator-hoofdtekst is bereikt of totdat er een yield break instructie is aangetroffen.

Iterators bieden geen ondersteuning voor de IEnumerator.Reset methode. Als u wilt herhalen vanaf het begin, moet u een nieuwe iterator verkrijgen. Het aanroepen van Reset de iterator die wordt geretourneerd door een iterator-methode genereert een NotSupportedException.

Zie de C#-taalspecificatie voor meer informatie.

Gebruik van Iterators

Met iterators kunt u de eenvoud van een foreach lus behouden wanneer u complexe code moet gebruiken om een lijstreeks te vullen. Dit kan handig zijn als u het volgende wilt doen:

  • Wijzig de lijstreeks na de eerste foreach lusiteratie.

  • Vermijd het volledig laden van een grote lijst vóór de eerste iteratie van een foreach lus. Een voorbeeld is een pagina ophalen om een batch tabelrijen te laden. Een ander voorbeeld is de EnumerateFiles methode, die iterators implementeert in .NET.

  • De lijst inkapselen in de iterator. In de iterator-methode kunt u de lijst maken en vervolgens elk resultaat in een lus opleveren.

Zie ook