Segredos do ForEach
Autor: Fabius Lovato | Publicado: 2010-07-11T23:21:00-03:00

Você sabe como funciona e como utilizar o ForEach no Delphi?
O suporte ao ForEach foi adicionado no Delphi 2005 e tem como finalidade oferecer uma interface de acesso aos itens das coleções (listas, filhas, filhas, array, conjuntos, vetores, ...) de forma simplificada e sem se preocupar com contadores ou com índices.

A sintaxe do ForEach é:
for item in collection do
begin
&nbsp;&nbsp;// 
end;
Note que na sitaxe do comando aparece a palavara reservada "in", o que reforça a idéia de um laço de repetição destinado a trabalhar com conjuntos/coleções, e é exatamente essa idéia que se deve manter em mente ao utilizar esse recurso em seu código fonte.
Dependendo do tipo de coleção que se está trabalhando, o comando terá uma característica específica, mas sempre mantendo a finalidade original.

Vamos ver alguns exemplos de uso.

1. Uso em conjuntos
Esse é um dos usos mais&nbsp;freqüentemente&nbsp;encontrado nos códigos fontes e que particularmente encorajo sua adoção e emprego.
Para quem não está muito familiarizado com o uso de conjuntos, o exemplo abaixo pode parecer&nbsp;estranho, num primeiro momento, mas numa segunda olhada verá que é muito intuitivo.

type
&nbsp; TWeekDay = (wdDomingo, wdSegunda, wdTerca, wdQuarta, wdQuinta, wdSexta, wdSabado);
&nbsp; TWeek = set of TWeekDay;
const
&nbsp; STRWEEKDAY: array[TWeekDay] of string =
&nbsp; ('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab');
var
&nbsp; lDay: TWeekDay;
&nbsp; lWorkDays: TWeek;
begin
&nbsp; lWorkDays := [wdSegunda, wdQuarta, wdSexta];

&nbsp; for lDay in lWorkDays do
&nbsp; begin
&nbsp;&nbsp;&nbsp; ShowMessage(STRWEEKDAY[lDay]);
&nbsp; end;
end;
O exemplo acima define o conjunto TWeek baseado nos dias da semana (TWeekDay) e usa essa definição para declarar a variável local lWorkDays.
Na primeira linha do bloco, nosso conjunto&nbsp;lWorkDays recebe a atribuição de três elementos, ou melhor, de três dias da semana e é utilizada no ForEach abaixo.
Como a coleção aqui é um conjunto, o laço de repetição do comando for ir percorrer todas as possíveis entradas do conjunto, mas somente executará as instruções do bloco quando a respectiva entrada realmente está presente no conjunto. Assim, no nosso exemplo, a função ShowMessage será chamada três vezes.
Reforço a vantagem do uso do ForEach com conjuntos porque além de intuitivo o código binário gerado é limpo, rápido e não consome recursos como o quando é utilizado com objetos.

2. Uso com strings
A utilização do ForEach com strings é um exemplo clássico do passado, mas que ao meu ver tem mais pontos negativos do que positivos.

const
&nbsp; S: string = 'Using ForEach with strings';
var
&nbsp; lChar: Char;
begin
&nbsp; for lChar in S do
&nbsp; begin
&nbsp;&nbsp;&nbsp; if lChar in ['a','e','i','o','u'] then
&nbsp;&nbsp;&nbsp; begin 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // ...
&nbsp;&nbsp;&nbsp; end;
&nbsp; end;
end;
Como dito anteriormente, o comando terá uma particularidade que depende do tipo de coleção utilizada, mas sempre centrado na idéia de acessar os elementos da coleção.
No exemplo acima nossa coleção abrange todo o espectro do tipo Char, nativo do Delphi, e o laço de repetição executará tantas vezes quanto for o número de caracteres da string.
Em outras palavras o comando for&nbsp;irá percorrer toda a string, atribuindo para a variável local lChar&nbsp;o valor de cada caracter.
Eu desencorajo a adoção dessa prática como alternativa para percorrer strings porque o código binário gerado é, do ponto de vista de micro-otimizações, bastante oneroso por requerer a manipulação do tipo string.

3. Uso com arrays estáticos
Também podemos utilizar o laço de repetição para percorrermos arrays de forma muito semelhante ao exemplo acima.

type
&nbsp;&nbsp;TMonthDays = array[1..31] of Boolean;
var
&nbsp;&nbsp;lWorkedDays: TMonthDays;
&nbsp;&nbsp;lDay: Boolean;
&nbsp;&nbsp;lCounter: Integer;
begin
&nbsp;&nbsp;FillMemory(@lWorkedDays, SizeOf(lWorkedDays), 0);

&nbsp;&nbsp;lWorkedDays[1] := True;
&nbsp;&nbsp;lWorkedDays[2] := True;
&nbsp;&nbsp;lWorkedDays[5] := True;

&nbsp;&nbsp;lCounter := 0;
&nbsp;&nbsp;for lDay in lWorkedDays do
&nbsp;&nbsp;begin
&nbsp;&nbsp; &nbsp;if lDay then
&nbsp;&nbsp; &nbsp; &nbsp;Inc(lCounter);
&nbsp;&nbsp;end;
&nbsp;&nbsp;ShowMessage('Sum: ' + IntToStr(lCounter));
end;
Observe que nesse nosso exemplo o índice do array não é utilizado, mas apenas o valor de cada entrada.
Aqui temos uma situação que merece um certo destaque por permitir ao código fonte um incremento na clareza e&nbsp;legibilidade, evitando a declaração de variáveis locais e especificando explicitamente os limites do array.
Se considerarmos uma abordagem mais clássica usando o laço for comum como, por exemplo:
var
&nbsp; i: Integer;
begin
&nbsp; // ...
&nbsp; for i = 1 to 31 do
&nbsp;&nbsp;&nbsp; // ...corremos o risco de introduzir um bug no sistema caso&nbsp;os limites&nbsp;do array seja moificado.
Obviamente podemos evitar esse tipo de bug removendo os hard codes,
&nbsp; for i =&nbsp;Low(TMonthDays) to&nbsp;High(TMonthDays) domas ainda assim dependeríamos do uso de uma variável como contador do laço de repetição.

4. Uso com arrays dinâmicos
Essa é uma situação levemente diferente do exemplo acima onde, em vez de termos um array com limites bem definidos, temos um array dinâmico&nbsp;cujo tamanho&nbsp;pode variar inclusive durante a execução do ForEach.
Aqui novamente estamos considerando que saber&nbsp;o índice&nbsp;atual do laço de repetição não é requerido, mas somente o valor de cada&nbsp;entrada do array.

var
&nbsp;&nbsp;lWorkedDays: array of Boolean;
&nbsp;&nbsp;lDay: Boolean;
&nbsp;&nbsp;lCounter: Integer;
begin
&nbsp;&nbsp;SetLength(lWorkedDays, 31);

&nbsp;&nbsp;lWorkedDays[0] := True;
&nbsp;&nbsp;lWorkedDays[1] := True;
&nbsp;&nbsp;lWorkedDays[4] := True;

&nbsp;&nbsp;lCounter := 0;
&nbsp;&nbsp;for lDay in lWorkedDays do
&nbsp;&nbsp;begin
&nbsp;&nbsp; &nbsp;if lDay then
&nbsp;&nbsp; &nbsp; &nbsp;Inc(lCounter);
&nbsp;&nbsp;end;
&nbsp;&nbsp;ShowMessage('Sum: ' + IntToStr(lCounter));
end;
Uma particularidade do array dinâmico é que a primeira entrada tem índice zero (0).
Utilizando o ForEach para percorrer o array, seja estático ou dinâmico, o limite inicial e final é tratado de forma transparente pelo compilador.

5. Usando com objetos
Esse é o último caso de uso do ForEach apresentado nesse artigo e o que merece mais atenção.
Como os tipos nativos (conjuntos, strings e arrays) o Delphi tem total conhecimento de como devem ser manipulados. Já quando a coleção é uma objeto, essa premissa não é verdadeira e, nesse caso, é você quem deve instruir o Delphi a acessar os itens do objeto de forma adequada.

Observe o exemplo abaixo.
var
&nbsp; lFiles: TStrings;
&nbsp; lFilename: string;
begin
&nbsp; // ...
&nbsp; for lFilename in lFiles do
&nbsp; begin
&nbsp;&nbsp;&nbsp; // ...
&nbsp; end;
end; 
No código acima&nbsp;a variável lFiles está sendo usada para armazenar uma lista de nomes de arquivos que serão tratados, e o laço de repetição irá acessar cada item dessa lista e repassar à variável local lFilename o valor do item (nesse caso, a string com o nome do arquivo).
O mesmo caso é válido para outras classes nativas do Delphi como por exemplo&nbsp;TTreeNode, TActionList, &nbsp;...
Bom aí vem a pergunta. Como o Delphi sabe qual é a lista&nbsp;de elementos da minha coleção quando se trata de um objeto?
Se você for na definição da classe TStrings encontrará um método com o nome "GetEnumerator" cujo retorno é um objeto do tipo TStringsEnumerator. E é exatamente esse método que será usado pelo ForEach para acessar os elementos da lista.

Para ser mais específico, um pré-requisito para usar o ForEach com objetos (instâncias de classes) é justamente a existência e implementação do método GetEnumerator pela classe do objeto.
Se você tentar usar um objeto qualquer o próprio compilador irá notificar sobre esse requisito, apresentando uma mensagem semelhante:

[DCC Error] Unit1.pas(145): E2431 for-in statement cannot operate on collection type 'TMyList' because 'TMyList' does not contain a member for 'GetEnumerator', or it is inaccessible

Além disso, o método GetEnumerator não pode retornar qualquer valor, ele deverá retornar uma instância cuja classe possua dois métodos específicos (GetCurrent e MoveNext) que serão utilizados para instruir o ForEach a percorrer os itens do objeto.
O método GetCurrent será utilizado para retornar o valor do atual elemento da lista e o método MoveNext irá&nbsp;posicionar&nbsp;o cursor no próximo item ou retornando False quando não há mais elementos na lista.

Vamos criar um exemplo simples para&nbsp;ilustrar&nbsp;como isso é feito.

TMyList = class;

TMyListEnumerator = class
private
&nbsp; FIndex: Integer;
&nbsp; FMyItems: TMyList;
public
&nbsp; constructor Create(ARef: TMyList);
&nbsp; function GetCurrent: Integer; inline;
&nbsp; function MoveNext: Boolean;
&nbsp; property Current: Integer read GetCurrent;
end;
A classe TMyListEnumerator será a responsável por acessar a lista de valores de TMyList.
O layout acima é exatamente o que você precisa implementar para todas as classes a qual deseja dar suporte ao ForEach, modificando apenas o tipo de retorno de acordo com o tipo de item das classes. Por exemplo, se coleção mantém uma lista de valores de ponto flutuante, substitua a o tipo "Integer" (no layout acima) por "float".

TMyList = class
&nbsp; protected
&nbsp; &nbsp; FName: string;
&nbsp; &nbsp; FItems: array of Integer;

&nbsp; public
&nbsp; &nbsp; destructor Destroy; override;
&nbsp; &nbsp; function Add(const Value: Integer): Integer;
&nbsp; &nbsp; function GetEnumerator: TMyListEnumerator;
end;
A classe TMyList é nossa coleção a qual mantém um array de valores inteiros (propriedade FItems).
Agora observe a implementação dos métodos da coleção. Nada de diferente a não ser pelo método GetEnumerator.

destructor TMyList.Destroy;
begin
&nbsp;&nbsp;SetLength(FItems, 0);
&nbsp;&nbsp;inherited;
end;

function TMyList.Add(const Value: Integer): Integer;
begin
&nbsp;&nbsp;Result := Length(FItems);
&nbsp;&nbsp;SetLength(FItems, Result + 1);

&nbsp;&nbsp;FItems[Result] := Value;
end;

function TMyList.GetEnumerator: TMyListEnumerator;
begin
&nbsp;&nbsp;Result := TMyListEnumerator.Create(Self);
end;
A implementação da nosso classe de enumeração não tem nada de complexo ou diferente, e o próprio código é auto explicativo.

constructor TMyListEnumerator.Create(ARef: TMyList);
begin
&nbsp;&nbsp;inherited Create;
&nbsp;&nbsp;FIndex := -1;
&nbsp;&nbsp;FMyItems := ARef;
end;

function TMyListEnumerator.GetCurrent: Integer;
begin
&nbsp;&nbsp;Result := FMyItems.FItems[FIndex];
end;

function TMyListEnumerator.MoveNext: Boolean;
begin
&nbsp;&nbsp;Result := FIndex &lt; (Length(FMyItems.FItems) - 1);
&nbsp;&nbsp;if Result then
&nbsp;&nbsp; &nbsp;Inc(FIndex);
end;
Caso tenha tido algum tipo de dificuldade, faça o download do projeto de&nbsp;exemplo&nbsp;(link http://rapidshare.com/files/406448596/ForEach.7z) que contém todo o código fonte utilizado nesse artigo.