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
//
end;
begin
//
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 freqüentemente 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 estranho, num primeiro momento, mas numa segunda olhada verá que é muito intuitivo.
type
TWeekDay = (wdDomingo, wdSegunda, wdTerca, wdQuarta, wdQuinta, wdSexta, wdSabado);
TWeek = set of TWeekDay;
const
STRWEEKDAY: array[TWeekDay] of string =
('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab');
var
lDay: TWeekDay;
lWorkDays: TWeek;
begin
lWorkDays := [wdSegunda, wdQuarta, wdSexta];
for lDay in lWorkDays do
begin
ShowMessage(STRWEEKDAY[lDay]);
end;
end;
TWeekDay = (wdDomingo, wdSegunda, wdTerca, wdQuarta, wdQuinta, wdSexta, wdSabado);
TWeek = set of TWeekDay;
const
STRWEEKDAY: array[TWeekDay] of string =
('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab');
var
lDay: TWeekDay;
lWorkDays: TWeek;
begin
lWorkDays := [wdSegunda, wdQuarta, wdSexta];
for lDay in lWorkDays do
begin
ShowMessage(STRWEEKDAY[lDay]);
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 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
S: string = 'Using ForEach with strings';
var
lChar: Char;
begin
for lChar in S do
begin
if lChar in ['a','e','i','o','u'] then
begin
// ...
end;
end;
end;
S: string = 'Using ForEach with strings';
var
lChar: Char;
begin
for lChar in S do
begin
if lChar in ['a','e','i','o','u'] then
begin
// ...
end;
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 irá percorrer toda a string, atribuindo para a variável local lChar 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
TMonthDays = array[1..31] of Boolean;
var
lWorkedDays: TMonthDays;
lDay: Boolean;
lCounter: Integer;
begin
FillMemory(@lWorkedDays, SizeOf(lWorkedDays), 0);
lWorkedDays[1] := True;
lWorkedDays[2] := True;
lWorkedDays[5] := True;
lCounter := 0;
for lDay in lWorkedDays do
begin
if lDay then
Inc(lCounter);
end;
ShowMessage('Sum: ' + IntToStr(lCounter));
end;
TMonthDays = array[1..31] of Boolean;
var
lWorkedDays: TMonthDays;
lDay: Boolean;
lCounter: Integer;
begin
FillMemory(@lWorkedDays, SizeOf(lWorkedDays), 0);
lWorkedDays[1] := True;
lWorkedDays[2] := True;
lWorkedDays[5] := True;
lCounter := 0;
for lDay in lWorkedDays do
begin
if lDay then
Inc(lCounter);
end;
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 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
i: Integer;
begin
// ...
for i = 1 to 31 do
// ...
corremos o risco de introduzir um bug no sistema caso os limites do array seja moificado.i: Integer;
begin
// ...
for i = 1 to 31 do
// ...
Obviamente podemos evitar esse tipo de bug removendo os hard codes,
for i = Low(TMonthDays) to High(TMonthDays) do
mas 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 cujo tamanho pode variar inclusive durante a execução do ForEach.
Aqui novamente estamos considerando que saber o índice atual do laço de repetição não é requerido, mas somente o valor de cada entrada do array.
var
lWorkedDays: array of Boolean;
lDay: Boolean;
lCounter: Integer;
begin
SetLength(lWorkedDays, 31);
lWorkedDays[0] := True;
lWorkedDays[1] := True;
lWorkedDays[4] := True;
lCounter := 0;
for lDay in lWorkedDays do
begin
if lDay then
Inc(lCounter);
end;
ShowMessage('Sum: ' + IntToStr(lCounter));
end;
lWorkedDays: array of Boolean;
lDay: Boolean;
lCounter: Integer;
begin
SetLength(lWorkedDays, 31);
lWorkedDays[0] := True;
lWorkedDays[1] := True;
lWorkedDays[4] := True;
lCounter := 0;
for lDay in lWorkedDays do
begin
if lDay then
Inc(lCounter);
end;
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
lFiles: TStrings;
lFilename: string;
begin
// ...
for lFilename in lFiles do
begin
// ...
end;
end;
lFiles: TStrings;
lFilename: string;
begin
// ...
for lFilename in lFiles do
begin
// ...
end;
end;
No código acima 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 TTreeNode, TActionList, ...
Bom aí vem a pergunta. Como o Delphi sabe qual é a lista 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á posicionar o cursor no próximo item ou retornando False quando não há mais elementos na lista.
Vamos criar um exemplo simples para ilustrar como isso é feito.
TMyList = class;
TMyListEnumerator = class
private
FIndex: Integer;
FMyItems: TMyList;
public
constructor Create(ARef: TMyList);
function GetCurrent: Integer; inline;
function MoveNext: Boolean;
property Current: Integer read GetCurrent;
end;
TMyListEnumerator = class
private
FIndex: Integer;
FMyItems: TMyList;
public
constructor Create(ARef: TMyList);
function GetCurrent: Integer; inline;
function MoveNext: Boolean;
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
protected
FName: string;
FItems: array of Integer;
public
destructor Destroy; override;
function Add(const Value: Integer): Integer;
function GetEnumerator: TMyListEnumerator;
end;
protected
FName: string;
FItems: array of Integer;
public
destructor Destroy; override;
function Add(const Value: Integer): Integer;
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
SetLength(FItems, 0);
inherited;
end;
function TMyList.Add(const Value: Integer): Integer;
begin
Result := Length(FItems);
SetLength(FItems, Result + 1);
FItems[Result] := Value;
end;
function TMyList.GetEnumerator: TMyListEnumerator;
begin
Result := TMyListEnumerator.Create(Self);
end;
begin
SetLength(FItems, 0);
inherited;
end;
function TMyList.Add(const Value: Integer): Integer;
begin
Result := Length(FItems);
SetLength(FItems, Result + 1);
FItems[Result] := Value;
end;
function TMyList.GetEnumerator: TMyListEnumerator;
begin
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
inherited Create;
FIndex := -1;
FMyItems := ARef;
end;
function TMyListEnumerator.GetCurrent: Integer;
begin
Result := FMyItems.FItems[FIndex];
end;
function TMyListEnumerator.MoveNext: Boolean;
begin
Result := FIndex < (Length(FMyItems.FItems) - 1);
if Result then
Inc(FIndex);
end;
begin
inherited Create;
FIndex := -1;
FMyItems := ARef;
end;
function TMyListEnumerator.GetCurrent: Integer;
begin
Result := FMyItems.FItems[FIndex];
end;
function TMyListEnumerator.MoveNext: Boolean;
begin
Result := FIndex < (Length(FMyItems.FItems) - 1);
if Result then
Inc(FIndex);
end;
Caso tenha tido algum tipo de dificuldade, faça o download do projeto de exemplo (link http://rapidshare.com/files/406448596/ForEach.7z) que contém todo o código fonte utilizado nesse artigo.
Nenhum comentário:
Postar um comentário