Iterador C#

Resumo : neste tutorial, você aprenderá sobre o padrão de iterador C# e como usá-lo para definir uma interface que itera os elementos em uma coleção.

Introdução ao padrão de design C# Iterator

O Iterator é um padrão de design comportamental que permite definir uma interface para iterar elementos de uma coleção sem expor sua implementação subjacente. O padrão Iterator faz isso separando a lógica de iteração do objeto de coleção.

O diagrama UML a seguir ilustra o padrão C# Iterator:

Iterador C#

O padrão Iterator tem os seguintes participantes:

  • Iteratoré uma interface que define um conjunto de métodos para iterar elementos em uma coleção. Normalmente, a Iteratorinterface consiste na Currentpropriedade, no MoveNext()método e Reset()no método. A Currentpropriedade retorna o elemento atual na coleção. O MoveNext()método retorna verdadeiro se o próximo elemento existir e avança para o próximo elemento ou retorna falso se não houver mais elementos para iterar. Este Reset()é um método opcional que redefine o iterador para o estado inicial.
  • ConcreteIteratoré uma classe que fornece uma implementação concreta da Iteratorinterface que itera elementos da coleção.
  • Aggregateé uma interface que possui o createIterator()método para criar um Iteratorobjeto. Normalmente, você define uma classe de coleção que implementa a Aggregateinterface e usa o Iteratorobjeto para iterar os elementos no objeto de coleção.
  • ConcreteAggregateé uma implementação concreta da Aggregateinterface que cria um ConcreteIteratorobjeto para iterar a coleção.

Exemplo de padrão de iterador C#

Suponha que você tenha uma coleção de meses de janeiro a dezembro e queira iterá-la na sequência de janeiro, fevereiro, março,… dezembro.

Além disso, se o mês fiscal começar em abril em vez de janeiro, será necessário fornecer uma maneira de iterar os meses na seguinte sequência: abril, maio,… dezembro, janeiro, fevereiro, março.

Para conseguir esse tipo de iteração, você pode usar o padrão Iterator. Aqui estão as etapas:

Primeiro, defina uma Iteratorinterface que possua métodos para iterar meses:

public interface IMonthIterator
{
    string Current { get; }
    bool MoveNext();
    void Reset();
}Linguagem de código:  C#  ( cs )

Segundo, crie uma IAggregateinterface que defina um método para criar um IMonthIteratorobjeto:

public interface IAggregate
{
    IMonthIterator CreateIterator();
}Linguagem de código:  C#  ( cs )

Terceiro, defina a Monthscoleção que implementa a IAggregateinterface:

public class Months: IAggregate
{
    private readonly string[] _months = {
        "Jan","Feb","Mar",
        "Apr","May","Jun",
        "Jul","Aug","Sep",
        "Oct","Nov","Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IMonthIterator CreateIterator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}Linguagem de código:  C#  ( cs )

Na Monthsaula:

  • O _monthscampo privado contém uma série de meses de Janaté Dec.
  • O FiscalMonthStartpadrão da propriedade é 1, indicando que o mês fiscal começa em janeiro por padrão.
  • O CreateIterator()método retorna um new MonthIteratorque é o iterador concreto da IMonthIteratorinterface. O construtor do MonthIteratoraceita dois argumentos, o _monthsarray e o FiscalMonthStart.

Quarto, defina a MonthIteratorclasse que implementa a IMonthIteratorinterface:

public class MonthIterator: IMonthIterator
{
    private int _index;
    private int _count = 0;
    private readonly string[] _months;
    private readonly int _fiscalMonthStart;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 0 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;

        _index = _fiscalMonthStart - 2;
    }

    public string Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }

    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}Linguagem de código:  C#  ( cs )

Na MonthIteratoraula:

  • O _indexcampo contém a posição atual do mês na _monthsmatriz.
  • A _monthsmatriz armazena uma matriz de meses de janeiro a dezembro.
  • O _countcampo contém o número de meses que foram iterados. Seus valores vão de 1 a 12. Se você iterar todos os meses, o _count será 12 e não haverá mais nenhum próximo elemento no _monthsretorno.
  • Armazena _fiscalMonthStarto número do mês em que o mês fiscal começará, por exemplo _fiscalMonthStart, é 1, o que significa que o mês fiscal começará em janeiro.
  • O construtor valida o _monthsarray para garantir que ele tenha 12 elementos e também garante que o valor de fiscalMonthStartseja de 1 a 12. Ele atribui _monthse _ fiscalMonthStartcampos e inicializa o _indexcampo.
  • O Currentgetter gera um IndexOutOfRangeExceptionerro se _indexnão estiver no intervalo válido de 1 a 12, aumenta o _counte retorna o elemento atual na _monthsmatriz com base no _index.
  • O MoveNext()método retorna falso se _countfor maior que 12. Caso contrário, aumenta em _indexum e retorna verdadeiro. Se _indexfor igual ao comprimento da _monthmatriz, redefina-o para zero.
  • O Reset()método redefine o _countpara zero e _indexpara _fiscalMonthStart - 2.

Por fim, crie um programa que use o iterador para iterar ao longo dos meses com o mês fiscal começando em janeiro e abril:

public class Program
{
    public static void Main(string[] args)
    {
        var months = new Months();

        // fiscal month start in Jan by default
        Console.WriteLine("Fiscal month start in January:");
        var iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;

        iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }
    }
}Linguagem de código:  C#  ( cs )

Saída:

Fiscal month start in January:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Fiscal month start in April:
Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb MarLinguagem de código:  texto simples  ( texto simples )

Junte tudo:

namespace IteratorPattern;

public interface IMonthIterator
{
    string Current {  get; }
    bool MoveNext();
    void Reset();
}

public interface IAggregate
{
    IMonthIterator CreateIterator();
}

public class Months : IAggregate
{
    private readonly string[] _months = {
        "Jan","Feb","Mar",
        "Apr","May","Jun",
        "Jul","Aug","Sep",
        "Oct","Nov","Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IMonthIterator CreateIterator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}

public class MonthIterator : IMonthIterator
{
    private int _index;
    private int _count = 0;
    private readonly string[] _months;
    private readonly int _fiscalMonthStart;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 0 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;

        _index = _fiscalMonthStart - 2;
    }

    public string Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }

    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}

public class Program
{
    public static void Run(string[] args)
    {
        var months = new Months();

        // fiscal month start in Jan by default
        Console.WriteLine("Fiscal month start in January:");
        var iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;

        iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }
    }
}Linguagem de código:  C#  ( cs )

Implementando o padrão Iterator em C# usando interfaces IEnumerable e IEnumerator

C# oferece suporte ao padrão de design Iterator pronto para uso por meio das interfaces IEnumeratore IEnumerable. Para o código C# moderno, você deve usar IEnumerator<T>e IEnumerable<T>em vez disso.

Além disso, C# permite iterar sobre os elementos de uma coleção que implementa a IEnumerableinterface <T> usando o foreachloop.

Em C#, um iterador também é chamado de enumerador. A IEnumerableinterface possui o GetEnumerator()método que retorna um enumerador para iterar os elementos de uma coleção. É equivalente à interface Aggregate.

A IEnumeratorinterface possui a Currentpropriedade que retorna o elemento atual da coleção, MoveNext()avança o enumerador para o próximo elemento da coleção e o Reset()método coloca o enumerador em sua posição inicial. É IEnumeratorequivalente à interface do Iterador.

O exemplo a seguir mostra como usar as interfaces IEnumablee IEnumeratorpara implementar o padrão Iterator.

Primeiro, defina a Monthsclasse que implementa a IEnumerableinterface. O GetEnumerator()método deve retornar uma instância da IEnumeratorinterface. Neste caso, ele retorna uma instância da MonthIteratorclasse:

public class Months : IEnumerable
{
    private readonly string[] _months = {
        "Jan","Feb", "Mar",
        "Apr","May", "Jun",
        "Jul","Aug", "Sep",
        "Oct","Nov", "Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IEnumerator GetEnumerator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}Linguagem de código:  C#  ( cs )

Segundo, defina a MonthIteratorclasse que implementa a IEnumeratorinterface:

public class MonthIterator : IEnumerator
{
    private int _index;
    private int _count = 0;
    private readonly int _fiscalMonthStart = 0;
    private readonly string[] _months;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 1 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;
        _index = _fiscalMonthStart - 2;
    }
    public object Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }


    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}Linguagem de código:  C#  ( cs )

Terceiro, use o foreachpara iterar os elementos da Monthscoleção:

public class Program
{
    public static void Main(string[] args)
    {
        var months = new Months();

        Console.WriteLine("Fiscal month start in January:");
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }
    }
}Linguagem de código:  C#  ( cs )

Saída:

Fiscal month start in January:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Fiscal month start in April:
Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb MarLinguagem de código:  C#  ( cs )

Junte tudo:

using System.Collections;

namespace IteratorPattern;

public class Months : IEnumerable
{
    private readonly string[] _months = {
        "Jan","Feb", "Mar",
        "Apr","May", "Jun",
        "Jul","Aug", "Sep",
        "Oct","Nov", "Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IEnumerator GetEnumerator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}

public class MonthIterator : IEnumerator
{
    private int _index;
    private int _count = 0;
    private readonly int _fiscalMonthStart = 0;
    private readonly string[] _months;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 1 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;
        _index = _fiscalMonthStart - 2;
    }
    public object Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }


    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var months = new Months();

        Console.WriteLine("Fiscal month start in January:");
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }
    }
}Linguagem de código:  C#  ( cs )

Resumo

  • Use o padrão Iterator para definir uma interface para iterar elementos de uma coleção.
  • Uso IEnumerable<T>e IEnumerator<T>interfaces para implementar o padrão de iterador em C#.
  • Use foreachpara iterar elementos em uma coleção que implementa a IEnumerable<T>interface.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *