Home > Delphi > Varrendo DataSet com Métodos Anônimos

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.

Categories: Delphi Tags:
  1. Marcos Douglas
    January 27th, 2009 at 08:13 | #1

    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.

  2. January 27th, 2009 at 08:29 | #2

    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.

  3. Magno Machado
    January 27th, 2009 at 10:19 | #3

    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.

  4. January 27th, 2009 at 10:30 | #4

    Magno, conheço e gosto muito dessa implementação também, boa dica! Por favor, pode postar o link, sem problemas.

  5. Marcos Douglas
    January 27th, 2009 at 10:41 | #5

    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?

  6. Cristian
    January 27th, 2009 at 10:55 | #6

    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

  7. Magno Machado
    January 29th, 2009 at 21:24 | #7

    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

  8. Elton
    January 31st, 2009 at 05:25 | #8

    nossa… código repetido é a coisa q eu mais tenho

  9. Frainer
    February 14th, 2009 at 02:02 | #9

    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!

  10. February 14th, 2009 at 16:08 | #10

    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.

  11. Frainer
    February 17th, 2009 at 23:16 | #11

    Vc poderia dar um exemplo?

  12. Cesar
    March 5th, 2009 at 13:47 | #12

    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;

  13. Eduardo
    March 17th, 2009 at 23:16 | #13

    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;

  14. March 20th, 2009 at 22:40 | #14

    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.

  15. March 21st, 2009 at 00:16 | #15

    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. :)

  16. June 12th, 2009 at 20:00 | #16

    Brilliant! Thank-you!

  17. June 22nd, 2009 at 15:12 | #17

    Realmente muito bom!

  18. Fabricio
    August 10th, 2009 at 09:40 | #18

    @Jim McKeeth
    Do you read portuguese?

  19. Fabricio
    August 10th, 2009 at 09:47 | #19

    @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.

  1. No trackbacks yet.