Varrendo DataSet com Métodos Anônimos
Métodos anônimos é um dos novos recursos trazidos pelo Delphi 2009. De início ele parece meio estranho e podemos levar um tempo para nos acostumar com a sintaxe, mas podem ser bem úteis em alguns casos como o que eu vou mostrar agora.
Quantas vezes você já não precisou escrever código para varrer um dataset do início ao fim?
DataSet.First; while not DataSet.Eof do begin // // Faz alguma coisa com o registro atual // DataSet.Next; end;
Isso pode ficar ainda maior se você precisar usar DisableControls ou ainda restaurar o ponteiro do registro do DataSet após a varredura:
var Bookmark: TBookmark; begin DataSet.DisableControls; try Bookmark := DataSet.Bookmark; DataSet.First; while not DataSet.Eof do begin // // Faz alguma coisa com o registro atual // DataSet.Next; end; DataSet.Bookmark := Bookmark; finally DataSet.EnableControls; end; end;
Esse código todo é praticamente repetido em todas as varreduras que precisamos fazer em um DataSet. Então seria muito útil se pudéssemos extrair esse código e trocar apenas a parte que “faz alguma coisa com o registro atual”.
É aqui que os métodos anônimos nos ajudam muito.
Na unit SysUtils do Delphi, existe um tipo TProc declarado desta forma:
type TProc = reference to procedure;
E vamos usá-lo para criar uma procedure que recebe um dataset e um método anônimo como parâmetros:
procedure ForEach(DataSet: TDataSet; Proc: TProc); var Bookmark: TBookmark; begin DataSet.DisableControls; try Bookmark := DataSet.Bookmark; DataSet.First; while not DataSet.Eof do begin Proc; DataSet.Next; end; DataSet.Bookmark := Bookmark; finally DataSet.EnableControls; end; end;
E com isso podemos chamar a procedure dessa forma:
ForEach(Table1, procedure begin ShowMessage(Table1NAME.Value) end);
Como eu disse, a sintaxe inicialmente é meio estranha, mas com o tempo você acostuma. No exemplo acima eu codifiquei uma procedure sem nome e sem parâmetros e passei para ser executado em cada registro. Eu tinha um dataset chamado Table1 no form. ShowMessage será chamado para cada registro. Além disso, ele desativa os controles conectados ao dataset e restaura o ponteiro no final.
Perceberam como isso facilita nossa vida? Então pronto, pode ir remover o monte de código duplicado que você tem em seus aplicativos.
Existe ainda uma idéia mais legal que é transformar esse recurso em um helper para a classe TDataSet, mas isso eu vou deixar para vocês como lição de casa ou para um próximo post aqui no blog.
Update: Eu já estava prevendo que alguém comentasse isso. Sim, eu sei que já era possível antes com ponteiros de funções, mas com métodos anônimos é muito mais legal e é uma forma de tirar proveito dessa novidade do Delphi 2009. Para quem ainda não tem o Delphi 2009, vale a dica do amigo Marcos Douglas, postada nos comentários.





Erick,
Não é por causa desta “novidade” que agora podemos remover um monte de código duplicado. Veja que isto é possível até no Delphi 7 (com a única “desvantagem” de não poder utilizar uma procedure anônima).
Veja o código de um Form abaixo, com as devidas alterações para ser compilado no Delphi 7:
type
TProc = procedure of object;
TForm1 = class(TForm)
Button1: TButton;
Table1: TADOTable;
ADOConnection1: TADOConnection;
Table1EmpNo: TIntegerField;
Table1LastName: TWideStringField;
Table1FirstName: TWideStringField;
Table1PhoneExt: TWideStringField;
Table1HireDate: TDateTimeField;
Table1Salary: TFloatField;
procedure Button1Click(Sender: TObject);
private
procedure MyProc;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure ForEach(DataSet: TDataSet; Proc: TProc);
var
Bookmark: TBookmark;
begin
DataSet.DisableControls;
try
Bookmark := DataSet.GetBookmark;
DataSet.First;
while not DataSet.Eof do
begin
Proc;
DataSet.Next;
end;
DataSet.GotoBookmark(Bookmark)
finally
DataSet.FreeBookmark(Bookmark);
DataSet.EnableControls;
end;
end;
{ TForm1 }
procedure TForm1.MyProc;
begin
ShowMessage(Table1FirstName.value)
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ForEach(Table1, MyProc);
end;
end.
Sim Marcos, eu sei, e eu nem disse que só era possível fazer isso com métodos anônimos. Só disse que eles nos ajudavam muito.
De qualquer forma, obrigado por ter comentado, certamente ajudará quem não tem o Delphi 2009 ainda.
Muito interessante esse uso dos métodos anonimos, Erick!
Eu uso faz algum tempo algo que criei com esse mesmo objetivo (eliminar esse codigo repetitivo de disable/enablecontrols e salvar/restaurar bookmark)… O bom é que é perfeitamente funcional mesmo sem métodos anonimos.. eu uso dessa forma:
var
I: IDataSetIterator;
begin
I := Iterator(MeuDataSet);
while I.Next do
ShowMessage(MeuDataSet.FieldByName(‘nome’).AsString);
Postei a implementação disso há alguns dias em meu recem-nascido blog, só não coloco o link aqui para os interessados porque não sei se você se importaria.
Magno, conheço e gosto muito dessa implementação também, boa dica! Por favor, pode postar o link, sem problemas.
Olá Erick,
Não quis dizer que só dava pra fazer isso com métodos anônimos. Me referi mais a parte de remover códigos duplicados, utilizando sua abordagem, porém com a versão 7.
Achei interessante os métodos anônimos (que já são utilizados no Java ou Ruby) porém isso pode trazer mais código duplicado. Imagine que o programador faz um método anônimo de 5 linhas, rapidamente. Depois de um tempo, em outra parte de código, é possível que ele precise das mesmas 5 linhas de código. Aí o programador preguiçoso copia/cola as 5 linhas ou invés de extrair e construir um método; bem, mas aí isso vai depender de cada um
Forte abraço,
Marcos Douglas.
PS: É possível deixar o código do meu primeiro comentário com identação?
Legal, isso realmente vai ajudar a deixar o codigo mais enXuto rsrs
pois pode ser aplicados em varias ocasioes, tipo varrer um tstringlist, onde precisa declarar uma variavel integer, por um FOR, contar o total de linhas e nao esquecer do -1, etc, etc muito bom mesmo…
legal o post, qualquer novidade post mais aih para nós
Aqui vai o prometido link:
http://blog.magnomp.net/?p=10
Espero que isso sirva para outros da mesma forma como tem servido para mim, isso realmente poupa uma boa quantidade de código repetitivo
nossa… código repetido é a coisa q eu mais tenho
Erick é possivel criar este metodo anonimo com parametro?
No delphi eu uso esta tecnica a muito tempo, porem nunca consegui utilizar parametro.
Excelente post, parabéns!
Sim, é possível criar com parâmetros. Além disso o método anônimo também pode ser uma função e retornar um valor.
Vc poderia dar um exemplo?
Taí Frainer!
type
TProcFieldsDataSet = reference to procedure(RecNo : integer; Fields : TFields);
// método
procedure ForeachInDataSets(DataSet : TDataSet; ProcFieldsDataSet : TProcFieldsDataSet);
begin
DataSet.DisableControls;
try
DataSet.First;
while not DataSet.Eof do
begin
ProcFieldsDataSet(DataSet.RecNo, DataSet.Fields);
DataSet.Next;
end;
finally
DataSet.EnableControls;
end;
end;
// Exemplo:
procedure TForm1.Button1Click(Sender: TObject);
var
Soma : Currency;
begin
Soma := 0;
// Considerando que em ClientDataSet1 tem um campo chamado Valor
ForeachInDataSets(ClientDataSet1, procedure(RecNo : integer; Fields : TFields)
begin
Soma := Soma + Fields.FieldByName(‘VALOR’).AsCurrency;
end);
ShowMessage(CurrtoStr(Soma));
end;
Galera, boa noite.
Comecei a estudar estes metódos anônimos há alguns dias e como não gosto muito desta bagunça que eles trouxeram, encapsulei os métodos em procedures separadas, que por motivos transacionais, utilizam parâmetros do tipo TCustomConnection. No mesmo exemplo do ForEach, eu tenho uma procedure que executa diversas operações transacionais utilizando ADODataSets e kbmMemTables.
Sei que os exemplos abaixo ficarão confusos, mas foi só para demonstrar como acabei precisando implementar uma TConnectionProc;
– DataSet, geralmente eu passo uma kbmMemTable.
– ConnectionProc, é uma procedure que possui TCustomConnection como parâmetro.
– Connection, é o mesmo parâmetro utilizado em ConnectionProc, porém não consegui fazer de outro modo. Este TCustomConnection vem do ADODataSet de onde foram importados os dados para a kbmMemTable, porém como a kbmMemTable não possui a propriedade Connection nativa, preciso passar o parâmetro da ADODataSet.Connection para dentro desta função via parâmetro mesmo.
Finalmente, acabei precisando fazer o seguinte:
TConnectionProc = procedure(Connection: TCustomConnection) of Object;
procedure ForEach(DataSet: TDataSet; ConnectionProc: TConnectionProc; Connection: TCustomConnection);
var
bmPos: TBookmark;
begin
with DataSet do
begin
try
DisableControls;
bmPos := Bookmark;
First;
while not Eof do
begin
ConnectionProc( Connection );
Next;
end;
finally
BookMark := bmPos;
EnableControls;
end;
end;
end;
procedure ReservaOSPecasAplicadas(Conexao: TCustomConnection);
begin
(…)
end;
procedure GravaDadosGravacao;
var
Conexao: TCustomConnection;
ConnectionProc: TConnectionProc;
begin
( … )
ConnectionProc := ReservaOSPecasAplicadas;
DMP.ForEach( kbmOSServicoLaboratPecasAplicadas, ConnectionProc, Conexao );
(…)
end;
Copiando do CG forum hoje:
“Good code is its own best documentation. As you’re about to add
a comment, ask yourself, ‘How can I improve the code so that
this comment isn’t needed?’ Improve the code and then document
it to make it even clearer.” — Steve McConnell Code Complete
99% das implementações usando métodos anônimos em Delphi que vi até hoje diziam assim em sua primeira linha (Parafraseando Obama):
// YES WE CAN!
Ou seja… Sempre fizemos, nunca foi impecílio, só que agora tem uma forma mais estranha de fazer que permite construções realmente bizarras, tipo, uma chamada a um método que se estende por 100 linhas, com 3 ou 4 outros métodos sendo passado como parâmetros…
Ao invés de gastarem a energia (pouca) que resta na CodeGear fazendo por exemplo uma RTTI mais poderosa, fizeram isto aí… lamentável.
Best regards.
Alexandre, concordo que métodos anônimos não são tão importantes, mas já que eles foram criados e já estão a disposição, acho melhor tirar proveito.
Brilliant! Thank-you!
Realmente muito bom!
@Jim McKeeth
Do you read portuguese?
@Alexandre Machado
Ao invés de gastarem a energia (pouca) que resta na CodeGear fazendo por exemplo uma RTTI mais poderosa, fizeram isto aí… lamentável.
Primeiro ponto: pouca energia? Cá entre nós, entre o D2006 e o D2009 se corrigiu mais erros novos e antigos do que entre 1995 e 2002… Desde que os profissionais tomaram conta de si mesmos, nunca se viu tanto qc# sendo resolvido. Isso, para mim, é sinal de MUITA energia.
Segundo: anom methods (em alguns lugares, closures) são base para um sem-número de outras features possíveis. Creio que seja esta a causa deles já terem sido implementados.
@Magno Machado
Magno , vc poderia disponibilizar a unit com o IDataSetItator ?
O link informando já não existe mais.
Valeu!
@Wagner Freitas
Pior que eu acho que não tenho mais o código. Vou ver se encontro, ou implemento novamente (era bem simples) e posto em algum lugar.