Olá

Hoje irei falar de um recurso muito interessante, porém acredito ainda ser pouco utilizado pelos desenvolvedores Delphi. Estou falando do Anonymous Method (Métodos Anônimos).

Este recurso é assim definido no site da Embarcadero:

Como o nome sugere, um método anônimo é um procedimento ou função que não tem um nome associado a ele. Um método anônimo trata de um bloco de código como uma entidade que pode ser atribuído a uma variável ou usado como um parâmetro para um método. Além disso, um método anônimo pode se referir a variáveis ​​e ligar os valores às variáveis ​​no contexto em que o método é definido. Métodos anônimos pode ser definido e utilizado com a sintaxe simples.

O type do método anônimo é declarado como uma referência a um método:

type
TFuncOfInt = reference to function(x: Integer): Integer;

Esta declaração indica que o método anônimo:

– é uma função
– recebe um parâmetro inteiro
– retorna um valor inteiro.

Com base nesta definição (e você que é leitor assíduo deste blog já sabe :)), iremos colocar isso em prática utilizando um exemplo simples (que não deve ser encarado como um caso real mas sim para efeitos didáticos). Neste exemplo, utilizando ainda o Delphi XE2, tentarei simular o que acontece geralmente em nossos sistemas quando não nos preocupamos com a repetição de código. Vamos lá!

Nosso exemplo irá utilizar um Form, quatro botões (para as operações soma, média, maior e menor) e um memo (que mostrará os passos executados), como pode ser visto na imagem abaixo:

Iremos utilizar um objeto chamado Lista (TStringList):

private
    { Private declarations }
    Lista: TStringList;

No Oncreate do nosso form, criamos o objeto e adicionamos algumas strings:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Lista := TStringList.Create;
  Lista.Add('1');
  Lista.Add('5');
  Lista.Add('3');
  Lista.Add('9');
  Lista.Add('7');
  Lista.Add('16');
  Lista.Add('10');
end;

No OnDestroy, destruimos a lista:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Lista.Free;
end;

No Onclick do primeiro botão, faremos:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  soma: integer;
begin
  soma := 0;
  memo1.lines.Clear;
  Memo1.Lines.Add('Iniciando processo...');
  for i := 0 to Lista.Count - 1 do
  begin
    Memo1.Lines.Add('Item ' + inttostr(i) + ': ' + lista.Strings[i]);
    soma := soma + StrToInt(Lista.Strings[i]);
  end;
  Memo1.Lines.Add('Processo finalizado');
  Memo1.Lines.Add('Linhas processadas: '+inttostr(lista.Count));
  Memo1.Lines.Add('Soma: '+inttostr(soma));
end;

No código acima, iremos percorrer a lista de strings, pegar a string, converter para inteiro e o resultado será somado à variável soma (não diga?!). Por fim, o memo será atualizado com o resultado da operação.

Executando o aplicativo e clicando no primeiro botão (soma), obteremos o seguinte:

Vamos agora fazer a codificação dos demais botões:

//Média da lista
procedure TForm1.Button2Click(Sender: TObject);
var
  i: integer;
  soma: integer;
  media: Real;
begin
  soma := 0;
  memo1.lines.Clear;
  Memo1.Lines.Add('Iniciando processo...');
  for i := 0 to Lista.Count - 1 do
  begin
    Memo1.Lines.Add('Item ' + inttostr(i) + ': ' + lista.Strings[i]);
    soma := soma + StrToInt(Lista.Strings[i]);
  end;
  Memo1.Lines.Add('Processo finalizado');
  Memo1.Lines.Add('Linhas processadas: '+inttostr(lista.Count));
  media := soma / Lista.Count;
  Memo1.Lines.Add('Média: '+formatfloat(',0.000', media));
end;

//O maior número da lista
procedure TForm1.Button3Click(Sender: TObject);
var
  i: integer;
  maior: integer;
begin
  maior := 0;

  memo1.lines.Clear;
  Memo1.Lines.Add('Iniciando processo...');
  for i := 0 to Lista.Count - 1 do
  begin
    Memo1.Lines.Add('Item ' + inttostr(i) + ': ' + lista.Strings[i]);
    if maior<StrToInt(Lista.Strings[i]) then
     maior := StrToInt(Lista.Strings[i]);
  end;
  Memo1.Lines.Add('Processo finalizado');
  Memo1.Lines.Add('Linhas processadas: '+inttostr(lista.Count));

  Memo1.Lines.Add('Maior: '+inttostr(maior));
end;

//O menor número da Lista
procedure TForm1.Button4Click(Sender: TObject);
var
  i: integer;
  menor: integer;
begin
  menor := StrToInt(Lista.Strings[0]);

  memo1.lines.Clear;
  Memo1.Lines.Add('Iniciando processo...');
  for i := 0 to Lista.Count - 1 do
  begin
    Memo1.Lines.Add('Item ' + inttostr(i) + ': ' + lista.Strings[i]);
    if menor>StrToInt(Lista.Strings[i]) then
     menor := StrToInt(Lista.Strings[i]);
  end;
  Memo1.Lines.Add('Processo finalizado');
  Memo1.Lines.Add('Linhas processadas: '+inttostr(lista.Count));

  Memo1.Lines.Add('Menor: '+inttostr(menor));
end;

É fácil perceber a repetição exagerada de código, principalmente no loop. Como trata-se de um único form, rapidamente detectamos o problema. Mas quem já programa, desenvolvendo grandes sistemas comerciais por exemplo, sabe que isso é mais comum do que se imagina. Volta e meia estamos lá, gerando mais um loop em nossa lista, mais uma repetição em nosso código. Pode ser numa lista de strings, num dataset, num array, etc.

Existem alguns meios de se evitar este problema. E um deles, certamente, é por meio da utilização de Anonymous Methods.

O primeiro passo na utilização dos métodos anônimos é declarar na seção type o nome do nosso método (aqui, irei utilizar um outro form, para manter a versão sem o recurso em questão, mas vocês poderão utilizar o mesmo form):

type
  TAnonymLista = reference to procedure(a: Integer);

  TForm2 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
...

Note que o nosso método terá um parâmetro integer, que será o índice atual da lista de strings. Agora, iremos criar uma procedure que irá ter como um dos parâmetros, o nosso método anônimo:

procedure PercorreLista(ALista: TStringList; AnonymLista: TAnonymLista);
var
  i: integer;
begin
  with Form2 do
  begin
    memo1.lines.Clear;
    Memo1.Lines.Add('Iniciando processo...');
    for i := 0 to ALista.Count - 1 do
    begin
      Memo1.Lines.Add('Item ' + inttostr(i) + ': ' + lista.Strings[i]);
      AnonymLista(i);  //método anônimo!!!
    end;
    Memo1.Lines.Add('Processo finalizado');
    Memo1.Lines.Add('Linhas processadas: '+inttostr(lista.Count));
  end;
end;

Em cada loop, o AnonymLista é executado, passando o índice atual da lista que estamos percorrendo. Falta ainda alterar o código dos botões. Vamos começar com o primeiro botão:

procedure TForm2.Button1Click(Sender: TObject);
var
  soma: integer;
begin
  soma := 0;
  PercorreLista(Lista, procedure (a:Integer)
  begin
    soma := soma + strtoint(lista.strings[a]);
  end);
  Memo1.Lines.Add('Soma: '+inttostr(soma));
end;

Em vez de chamar o loop (for), chamamos a procedure PercorreLista, passando a lista e, finalmente, passando um método (veja que não tem nome) com as instruções do que deve ser feito.

Código dos demais botões:

procedure TForm2.Button2Click(Sender: TObject);
var
  soma: integer;
  media: Real;
begin
  soma := 0;
  PercorreLista(Lista, procedure(a:Integer)
  begin
    soma := soma + StrToInt(Lista.Strings[a]);
  end);
  media := soma / Lista.Count;
  Memo1.Lines.Add('Média: '+formatfloat(',0.000', media));
end;

procedure TForm2.Button3Click(Sender: TObject);
var
  maior: integer;
begin
  maior := 0;
  PercorreLista(lista, procedure(a:Integer)
  begin
    if maior<StrToInt(Lista.Strings[a]) then
     maior := StrToInt(Lista.Strings[a]);
  end);
  Memo1.Lines.Add('Maior: '+inttostr(maior));
end;

procedure TForm2.Button4Click(Sender: TObject);
var
  menor: integer;
begin
  menor := StrToInt(Lista.Strings[0]);
  PercorreLista(Lista, procedure(a:Integer)
  begin
    if menor>StrToInt(Lista.Strings[a]) then
     menor := StrToInt(Lista.Strings[a]);
  end);
  Memo1.Lines.Add('Menor: '+inttostr(menor));
end;

Como pode ser visto, eu padronizei a utilização da minha lista. O que se repetia, mandei para procedure PercorreLista.

Segue código completo do exemplo:

type
  TAnonymLista = reference to procedure(a: Integer);

  TForm2 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private declarations }
    Lista: TStringList;
  public
    { Public declarations }

  end;

  procedure PercorreLista(ALista: TStringList; AnonymLista: TAnonymLista);

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure PercorreLista(ALista: TStringList; AnonymLista: TAnonymLista);
var
  i: integer;
begin
  with Form2 do
  begin
    memo1.lines.Clear;
    Memo1.Lines.Add('Iniciando processo...');
    for i := 0 to ALista.Count - 1 do
    begin
      Memo1.Lines.Add('Item ' + inttostr(i) + ': ' + lista.Strings[i]);
      AnonymLista(i);
    end;
    Memo1.Lines.Add('Processo finalizado');
    Memo1.Lines.Add('Linhas processadas: '+inttostr(lista.Count));
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
var
  soma: integer;
begin
  soma := 0;
  PercorreLista(Lista, procedure(a:Integer)
  begin
    soma := soma + strtoint(lista.strings[a]);
  end);
  Memo1.Lines.Add('Soma: '+inttostr(soma));
end;

procedure TForm2.Button2Click(Sender: TObject);
var
  soma: integer;
  media: Real;
begin
  soma := 0;
  PercorreLista(Lista, procedure(a:Integer)
  begin
    soma := soma + StrToInt(Lista.Strings[a]);
  end);
  media := soma / Lista.Count;
  Memo1.Lines.Add('Média: '+formatfloat(',0.000', media));
end;

procedure TForm2.Button3Click(Sender: TObject);
var
  maior: integer;
begin
  maior := 0;
  PercorreLista(lista, procedure(a:Integer)
  begin
    if maior<StrToInt(Lista.Strings[a]) then
     maior := StrToInt(Lista.Strings[a]);
  end);
  Memo1.Lines.Add('Maior: '+inttostr(maior));
end;

procedure TForm2.Button4Click(Sender: TObject);
var
  menor: integer;
begin
  menor := StrToInt(Lista.Strings[0]);
  PercorreLista(Lista, procedure(a:Integer)
  begin
    if menor>StrToInt(Lista.Strings[a]) then
     menor := StrToInt(Lista.Strings[a]);
  end);
  Memo1.Lines.Add('Menor: '+inttostr(menor));
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  Lista := TStringList.Create;
  Lista.Add('1');
  Lista.Add('5');
  Lista.Add('3');
  Lista.Add('9');
  Lista.Add('7');
  Lista.Add('16');
  Lista.Add('10');
end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
  Lista.Free;
end;

Agora eu pergunto:

– Quantas vezes você programou o mesmo loop (com alguma alteração em seu interior) em partes distintas da sua aplicação?
– Quantas vezes você percorreu um dataset, seja para imprimir determinado registro seja para pegar o total de um campo?
– Já pensou em otimizar estes processos?

O Anonymous Methods poderia ser uma opção a ser analisada. Não estou dizendo que é a única forma, claro existem outras, mas é um recurso interessante.

Espero que tenham gostado. Se sim, clique em curtir na caixa do Facebook na lateral.

Abraços.

Desenvolvedor de software desde 1995. Em 1998, abriu sua própria empresa, a Lukas Sistemas, desde então passou a atender diversas empresas, principalmente autopeças. Apaixonado por Delphi, porém não o impede de flertar com outras linguagens sempre que possível. Mora na cidade de Balsas/MA com sua esposa e dois filhos.

6 thoughts on “Métodos Anônimos no Delphi”

  1. Luiz, sou programador há vários anos, já programei em Clipper, Cobol e outros, hoje programo em Delphi e quero deixar aqui meus parabéns a você pelo excelente blog, tenho me atualizado com relação as novas tecnologias incorporadas ao Delphi e este é um dos sites que muito tem me ajudado. Parabéns !

    1. Obrigado Claudius.

      Fico feliz que o conteúdo deste blog tenha lhe ajudado.

      Ultimamente estou um pouco afastado do blog, muito trabalho pendente, mas sempre que posso, coloco algo para os meus leitores.

      Sugiro que leia a série ORM Básico. Tem muita coisa interessante.

      Abraços.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.