By Properties Iterator mówiąc krótko jest to obiekt, który przemieszcza się po strukturze obiektu. Iterator zazwyczaj ma referencję do obecnego obiektu, do którego ma dostęp i posiada także metodę by przejść do następnego elementu.

 Mam także iteracje dwukierunkowe, które pozwalają ci także iść w przeciwną stronę.

W .NET mam już sposób na implementację tego wzorca . Jest nim interfejs IEnumerator<T> . Posiada on następujące metody:

  • Current : referuję się do obecnego obiektu w tej pozycji
  • MoveNext() : pozwala Ci przesunąć się do elementu następnego w tej kolekcji. Jeżeli operacja się powiodła otrzymujemy wartość true, jeśli nie to false
  • Reset() resetuje do pozycji startowej

Jak to działa? W praktyce, gdy piszesz taki kod:

foreach (var item in collection)
{

}

Tak naprawdę robisz to:

var enumerator = ((IEnumerable<Person>)y).GetEnumerator();
while (enumerator.MoveNext())
{
  temp = enumerator.Current;
  Console.WriteLine(temp);
}

Czyli by stworzyć klasę, która implementuje IEnumerable<T> musisz zaimplementować metodę GetEnumerator(), która zwróci IEnumerator<T>.

Bardzo rzadko się zdarza byś potrzebował swojego IEnumerator-a. Dzięki słowu kluczowemu yield możesz też napisać taki kod:

IEnumerable<int> GetSomeNumbers()
{
    yield return 3;
    yield return 2;
    yield return 1;
}

Cała implementacja tego wzorca zostaje przejęta przez kompilator C#. Poza tym, czy byś z niego skorzystał, gdy całą gamę kolekcji w C# jak List<T>.

Jakie jednak problemy możemy mieć, które by mogły zostać rozwiązanie przez ten wzorzec?

Iteracja po wartościach właściwości klasy, ale jak?

Nie wszystkie rzeczy zostały stworzone do łatwej iteracji . Nie może np. w C# łatwo przejść po wszystkich polach, właściwościach klasy. No, chyba że używasz refleksji, ale to komplikuje bardzo kod.

Powiedzmy, że tworzysz grę z magicznymi kartami. Te magiczne karty mają następujące atrybuty jak : Siła, Zręczność, Mądrość, Inteligencja, Zdrowie oraz Charyzma.

public class MagicCard
{
    public int Strength { get; set; }
    public int Agility { get; set; }
    public int Intelligence { get; set; }
    public int Wisdom { get; set; }
    public int Health { get; set; }
    public int Charisma { get; set; }
}

Teraz powiedzmy, że przy zdolnościach specjalnych chciał łączyć pewne cechy magicznej kart oraz także wszystkie je sumować.

public double SmartTalkPoints => Intelligence + Charisma;

public double ArtisticSoul => Wisdom + Charisma;

public double PowerPoints => Strength + Agility + Intelligence
+ Wisdom + Health + Charisma;

Kod może stawać się coraz bardziej złożony, jeśli zacznę dodawać kolejne parametry do mojej karty postaci.

A co się stanie, jeśli będę chciał określić średnią wartość tych wszystkich cech.

public double AverageStat => Power / 6.0;

6.0 to magiczna cyfra. To już wygląda nie ciekawie. A co by się stało, gdyby chciałbym pokazać punkty maksymalnej cechy swojej karty

public double MaxStat => Math.Max(
Math.Max(Strength, Agility), 
Math.Max(Math.Max(Intelligence, Wisdom),Math.Max(Health, Charisma)));

Nie wygląda to dobrze.

Pora skorzystać z techniki array-backed properties. Co na Polski można przetłumaczyć właściwości wspierane przez tablice.

Wszystkie cechy naszej karty będą znajdować się w tablicy

private int[] stats = new int[6];

Aby wiedzieć gdzie co jest będziemy korzystać ze stały indeksów.

private const int strength = 0;
public int Strength
{
    get => stats[strength];
    set => stats[strength] = value;
}

private const int agility = 1;
public int Agility
{
    get => stats[agility];
    set => stats[agility] = value;
}

private const int intelligence = 2;
public int Intelligence
{
    get => stats[intelligence];
    set => stats[intelligence] = value;
}

private const int wisdom = 3;
public int Wisdom
{
    get => stats[wisdom];
    set => stats[wisdom] = value;
}

private const int health = 4;
public int Health
{
    get => stats[health];
    set => stats[health] = value;
}

private const int charisma = 5;
public int Charisma
{
    get => stats[charisma];
    set => stats[charisma] = value;
}

Dzięki metodom LINQ wyliczenie sumy, wartości przeciętnej oraz maksymalnej to bułka z masłem.

public double Power => stats.Sum();

public double AverageStat => stats.Average();

public double MaxStat => stats.Max();

Poza tym skoro wszystkie cechy karty są tablicą to oznacza, że można po niej przejść pętlą foreach albo for.

Co można zrobić by ten kod był bardziej intucyjny możesz sprawić, aby cała klasa dziedziczyła po interfejsie IEnumerable<int>.  Czyli tak użyliśmy sensownie wzorca iterator xD

public class MagicCard : IEnumerable<int>
{
    private int[] stats = new int[6];

    IEnumerator IEnumerable.GetEnumerator()
    {
        return stats.AsEnumerable().GetEnumerator();
    }

    public IEnumerator<int> GetEnumerator()
    {
        return stats.AsEnumerable().GetEnumerator();
    }

    public int this[int index]
    {
        get => stats[index];
        set => stats[index] = value;
    }

    private const int strength = 0;
    public int Strength
    {
        get => stats[strength];
        set => stats[strength] = value;
    }
    private const int agility = 0;
    public int Agility
    {
        get => stats[agility];
        set => stats[agility] = value;
    }
    private const int intelligence = 0;
    public int Intelligence
    {
        get => stats[intelligence];
        set => stats[intelligence] = value;
    }
    private const int wisdom = 0;
    public int Wisdom
    {
        get => stats[wisdom];
        set => stats[wisdom] = value;
    }
    private const int health = 0;
    public int Health
    {
        get => stats[health];
        set => stats[health] = value;
    }
    private const int charisma = 0;
    public int Charisma
    {
        get => stats[charisma];
        set => stats[charisma] = value;
    }

    public double Power => stats.Sum();

    public double AverageStat => stats.Average();

    public double MaxStat => stats.Max();

}

To jest też jeden z moich momentów w karierze, w którym mogę napisać implementację indeksera dla kolekcji.

A tak na serio to powinien napisać wpis na temat tego, jak od podstaw stworzyć swojego własnego iteratora. Najlepszym przykładem do tego są struktury drzew, których akurat domyślnie w C# nie ma.

Jak jednak widzisz naprawdę istnieje mało przypadków by ten wzorzec implementować.