Geralmente o pessoal de Delphi conhece apenas o loop “for básico“, aquele “old school” :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
procedure Exemplo; var I: Integer; begin //Exemplo básico de loop for (crescente) for I := 1 to 10 do begin //Faça alguma coisa end; //Exemplo de loop for (decrescente) for I := 10 downto 1 do begin //Faça alguma coisa end; end; |
outros até conhecem o loop “for-in“, que é utilizado para iterar elementos de um conjunto:
Com strings:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure Exemplo; var Texto: String; Elemento: Char; begin Texto := 'Exemplo de loop for-in'; //Exemplo de loop for-in for Elemento in Texto do begin //Faça alguma coisa end; end; |
Com arrays:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
procedure Exemplo; var MeuArray: TMeuArray; I: Integer; begin MeuArray[0] := 0; MeuArray[1] := 10; MeuArray[2] := 20; for I in MeuArray do begin { Faça alguma coisa } end; end; |
Ele funciona também com outros tipos (sets, por exemplo). Mas o que nem todo mundo sabe, é que é possível criar seus próprios enumeradores.
Enumeradores (Enumerator)
Você pode criar seu próprio enumerador para realizar iterações utilizando o loop “for-in”. Vamos imaginar uma classe de pedidos que armazene informações de seus itens. Assim, gostaríamos de fazer uma iteração entre os itens do pedido, utilizando o loop “for-in”. A primeira coisa a ser feita é criar:
1 2 3 4 5 6 7 8 9 10 11 |
type TItemPedido = class strict private FCodProduto: Integer; FDescProduto: String; FValor: Extended; published property CodProduto: Integer read FCodProduto write FCodProduto; property DescProduto: String read FDescProduto write FDescProduto; property Valor: Extended read FValor write FValor; end; |
Basicamente, criamos uma classe para simbolizar os itens do pedido. Ela possui apenas três informações:
- CodProduto – O código do produto.
- DescProduto – A descrição do produto.
- Valor – Valor do produto.
Agora precisamos da classe do pedido:
1 2 3 |
TPedido = class end; |
Ela está bem simples, mas faremos algumas alterações daqui a pouco.
Precisamos criar uma classe enumeradora. Ela será responsável por armazenar toda a lógica de iteração do itens do pedido, tirando essa responsabilidade da classe de pedidos.
Uma coisa a saber sobre as classes enumeradoras, é que elas precisam obrigatoriamente possuir 2 métodos:
- Uma função pública chamada MoveNext.
- Uma propriedade read-only chamada Current.
A função MoveNext será chamada no loop para que o próximo elemento seja apresentado.
A propriedade será chamada para retornar o elemento atual da iteração.
Uma coisa a se observar aqui é que MoveNext sempre será chamado antes de Current, por isso, tome cuidado para que seu código trate esse comportamento.
Abaixo encontra-se a definição da classe enumeradora.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
type TMeuEnumerador = class strict private FIndex: Integer; FLista: TList<TItemPedido>; protected function GetCurrent: TItemPedido; public constructor Create(AListaItensPedido: TList<TItemPedido>); reintroduce; function MoveNext: Boolean; published property Current: TItemPedido read GetCurrent; end; |
Agora que já temos nosso enumerador, vamos voltar à classe de pedidos e adicionar o último método para que tudo isso funcione.
- Uma função pública chamada GetEnumerator.
GetEnumerator será o responsável por dizer ao compilador qual a classe responsavel pela iteração dos itens do pedido.
1 2 3 4 |
TPedido = class public function GetEnumerator: TMeuEnumerador; end; |
Colocando tudo para funcionar
Estabelecido as declarações, vamos para a implementação, começando com a classe enumeradora.
1 2 3 4 5 6 |
constructor TMeuEnumerador.Create(AListaItensPedido: TList<TItemPedido>); begin inherited Create; FIndex := -1; //Isso é importante! MoveNext é chamado sempre antes de GetCurrent! FLista := AListaItensPedido; end; |
A classe enumeradora possui um contador interno chamdo FIndex, que zeramos para começarmos a iteração, no momento da criação.
FLista é a lista de pedidos que serão iterados.
1 2 3 4 |
function TMeuEnumerador.GetCurrent: TItemPedido; begin Result := FLista[FIndex]; end; |
GetCurrent será a função utilizada para o retorno do item do pedido atual da iteração.
1 2 3 4 5 6 |
function TMeuEnumerador.MoveNext: Boolean; begin Result := FIndex < FLista.Count - 1; if Result then FIndex := FIndex + 1; end; |
MoveNext é a função responsável pela iteração dos itens do pedido, ou seja, aquela que movimenta o indice para “getCurrent” possa retornar novos itens do pedido.
Nada mais a se fazer com a classe enumeradora. Sua responsabilidade já foi satisfeita, ou seja, a iteração dos itens dentro da lista.
Voltando à classe de pedidos, devemos implementar a função GetEnumerator, para que o compilador saiba qual será nossa classe enumeradora:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function TPedido.GetEnumerator: TMeuEnumerador; var ItemPedido: TItemPedido; Lista: TList<TItemPedido>; begin {Lógica para obter a lista de pedidos} Lista := TList<TItemPedido>.Create; Lista.Clear; ItemPedido := TItemPedido.Create; ItemPedido.CodProduto := 1; ItemPedido.DescProduto := 'Produto 1'; ItemPedido.Valor := 10; Lista.Add(ItemPedido); ItemPedido := TItemPedido.Create; ItemPedido.CodProduto := 2; ItemPedido.DescProduto := 'Produto 2'; ItemPedido.Valor := 20; Lista.Add(ItemPedido); ItemPedido := TItemPedido.Create; ItemPedido.CodProduto := 3; ItemPedido.DescProduto := 'Produto 3'; ItemPedido.Valor := 30; Lista.Add(ItemPedido); {Cria o enumerado passando a lista de produtos} Result := TMeuEnumerador.Create(Lista); end; |
Veja que precisei criar os itens manualmente aqui, como forma elucidativa, mas provavelmente você teria uma outra classe para o retorno dos itens ou mesmo uma busca desses itens no banco de dados. O mais importante aqui é a instrução:
Result := TMeuEnumerador.Create(Lista);
Ela retorna a criação da nossa classe enumeradora.
Todo o mecanismo está criado. Podemos agora simplesmente fazer uso de nossa nova estrutura:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TForm1.Button1Click(Sender: TObject); var Pedido: TPedido; ItemPedido: TItemPedido; begin Pedido := TPedido.Create; try for ItemPedido in Pedido do begin WriteLn(ItemPedido.CodProduto.ToString + ' - ' + ItemPedido.DescProduto + ' - ' + ItemPedido.Valor.ToString); end; finally Pedido.Free; end; end; |
Veja como o loop ficou muito mais simples, mais “clean”. Essa é a vantagem desse tipo de estrutura, seu charme.
Pronto! Simples, eficiente e elegante. Execute e veja os resultados. Abaixo encontra-se o fonte completo do exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Generics.Collections, Vcl.StdCtrls; type TItemPedido = class strict private FCodProduto: Integer; FDescProduto: String; FValor: Extended; published property CodProduto: Integer read FCodProduto write FCodProduto; property DescProduto: String read FDescProduto write FDescProduto; property Valor: Extended read FValor write FValor; end; type TMeuEnumerador = class strict private FIndex: Integer; FLista: TList<TItemPedido>; protected function GetCurrent: TItemPedido; public constructor Create(AListaItensPedido: TList<TItemPedido>); reintroduce; function MoveNext: Boolean; published property Current: TItemPedido read GetCurrent; end; TPedido = class public function GetEnumerator: TMeuEnumerador; end; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); protected { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var Pedido: TPedido; ItemPedido: TItemPedido; begin Pedido := TPedido.Create; try for ItemPedido in Pedido do begin WriteLn(ItemPedido.CodProduto.ToString + ' - ' + ItemPedido.DescProduto + ' - ' + ItemPedido.Valor.ToString); end; finally Pedido.Free; end; end; { TPedido } function TPedido.GetEnumerator: TMeuEnumerador; var ItemPedido: TItemPedido; Lista: TList<TItemPedido>; begin {Lógica para obter a lista de pedidos} Lista := TList<TItemPedido>.Create; Lista.Clear; ItemPedido := TItemPedido.Create; ItemPedido.CodProduto := 1; ItemPedido.DescProduto := 'Produto 1'; ItemPedido.Valor := 10; Lista.Add(ItemPedido); ItemPedido := TItemPedido.Create; ItemPedido.CodProduto := 2; ItemPedido.DescProduto := 'Produto 2'; ItemPedido.Valor := 20; Lista.Add(ItemPedido); ItemPedido := TItemPedido.Create; ItemPedido.CodProduto := 3; ItemPedido.DescProduto := 'Produto 3'; ItemPedido.Valor := 30; Lista.Add(ItemPedido); {Cria o enumerado passando a lista de produtos} Result := TMeuEnumerador.Create(Lista); end; { TMeuEnumerador } constructor TMeuEnumerador.Create(AListaItensPedido: TList<TItemPedido>); begin inherited Create; FIndex := -1; //Isso é importante! MoveNext é chamado sempre antes de GetCurrent! FLista := AListaItensPedido; end; function TMeuEnumerador.GetCurrent: TItemPedido; begin Result := FLista[FIndex]; end; function TMeuEnumerador.MoveNext: Boolean; begin Result := FIndex < FLista.Count - 1; if Result then FIndex := FIndex + 1; end; procedure TForm1.FormCreate(Sender: TObject); begin AllocConsole; end; end. |
Pronto para utilizar enumeradores em seus projeto?