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:
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, aIterator
interface consiste naCurrent
propriedade, noMoveNext()
método eReset()
no método. ACurrent
propriedade retorna o elemento atual na coleção. OMoveNext()
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. EsteReset()
é um método opcional que redefine o iterador para o estado inicial.ConcreteIterator
é uma classe que fornece uma implementação concreta daIterator
interface que itera elementos da coleção.Aggregate
é uma interface que possui ocreateIterator()
método para criar umIterator
objeto. Normalmente, você define uma classe de coleção que implementa aAggregate
interface e usa oIterator
objeto para iterar os elementos no objeto de coleção.ConcreteAggregate
é uma implementação concreta daAggregate
interface que cria umConcreteIterator
objeto 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 Iterator
interface 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 IAggregate
interface que defina um método para criar um IMonthIterator
objeto:
public interface IAggregate
{
IMonthIterator CreateIterator();
}
Linguagem de código: C# ( cs )
Terceiro, defina a Months
coleção que implementa a IAggregate
interface:
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 Months
aula:
- O
_months
campo privado contém uma série de meses deJan
atéDec
. - O
FiscalMonthStart
padrão da propriedade é 1, indicando que o mês fiscal começa em janeiro por padrão. - O
CreateIterator()
método retorna um newMonthIterator
que é o iterador concreto daIMonthIterator
interface. O construtor doMonthIterator
aceita dois argumentos, o_months
array e oFiscalMonthStart
.
Quarto, defina a MonthIterator
classe que implementa a IMonthIterator
interface:
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 MonthIterator
aula:
- O
_index
campo contém a posição atual do mês na_months
matriz. - A
_months
matriz armazena uma matriz de meses de janeiro a dezembro. - O
_count
campo 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_months
retorno. - Armazena
_fiscalMonthStart
o 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
_months
array para garantir que ele tenha 12 elementos e também garante que o valor defiscalMonthStart
seja de 1 a 12. Ele atribui_months
e _fiscalMonthStart
campos e inicializa o_index
campo. - O
Current
getter gera umIndexOutOfRangeException
erro se_index
não estiver no intervalo válido de 1 a 12, aumenta o_count
e retorna o elemento atual na_months
matriz com base no_index
. - O
MoveNext()
método retorna falso se_count
for maior que 12. Caso contrário, aumenta em_index
um e retorna verdadeiro. Se_index
for igual ao comprimento da_month
matriz, redefina-o para zero. - O
Reset()
método redefine o_count
para zero e_index
para_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 Mar
Linguagem 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 IEnumerator
e 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 IEnumerable
interface <T> usando o foreach
loop.
Em C#, um iterador também é chamado de enumerador. A IEnumerable
interface possui o GetEnumerator()
método que retorna um enumerador para iterar os elementos de uma coleção. É equivalente à interface Aggregate.
A IEnumerator
interface possui a Current
propriedade 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. É IEnumerator
equivalente à interface do Iterador.
O exemplo a seguir mostra como usar as interfaces IEnumable
e IEnumerator
para implementar o padrão Iterator.
Primeiro, defina a Months
classe que implementa a IEnumerable
interface. O GetEnumerator()
método deve retornar uma instância da IEnumerator
interface. Neste caso, ele retorna uma instância da MonthIterator
classe:
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 MonthIterator
classe que implementa a IEnumerator
interface:
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 foreach
para iterar os elementos da Months
coleçã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 Mar
Linguagem 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>
eIEnumerator<T>
interfaces para implementar o padrão de iterador em C#. - Use
foreach
para iterar elementos em uma coleção que implementa aIEnumerable<T>
interface.