Atenção: esse artigo tem uma palestra complementar. Clique e assista!

Do que trata o artigo

Nesse artigo abordaremos Generics, tanto os tipos já existentes no Delphi como a criação de nossos próprios tipos. Criaremos métodos genéricos, classes genéricas e coleções genéricas. Também mostraremos como podemos tornar nossas aplicações mais flexíveis, porém coerentes utilizando constraints.

Para que serve

Generics é um novo recurso do Delphi que permite que criemos tipos parametrizados. Com tipos parametrizados queremos dizer que um parâmetro que passamos para nossa classe ou método será substituído por um tipo. Assim como os parâmetros de um método são substituídos por valores ou endereços de variáveis, o parâmetro genérico é substituído por um tipo de dado, podendo ser um tipo primitivo, estruturado ou uma classe.

Em que situação o tema é útil

Sempre que precisamos declarar um atributo, método ou variável em uma classe, mas não sabemos de que tipo criar, ou o tipo deve ser variável em tempo de compilação. Não se aplica, porém, nos casos em que o tipo é usado dentro da própria classe ou alterado em tempo de execução. Para isso usamos interfaces. Usando Generics podemos poupar linhas de código e reaproveitar muitas linhas, porque não criamos uma lista infindável de classes irmãs, quase iguais, para trabalhar com tipos de dados diferentes. Também não criamos uma longa lista de métodos sobrecarregados simplesmente para mudar o tipo dos argumentos ou retornos.

Resumo do DevMan

Explicaremos de maneira direta e com exemplos como usar a nova sintaxe do Delphi para tipos parametrizados, conhecidos como Generics. Mostraremos como usar a sintaxe, como criar seus próprios tipos parametrizados, como criar métodos parametrizados e como um parâmetro genérico interfere no próprio funcionamento da classe em si. Mostraremos também alguns tipos genéricos do próprio Delphi e algumas maneiras práticas de se aplicar o que aprendemos. Por último, falaremos sobre como limitar o tipo genérico com constraints, tanto para não permitir que qualquer tipo de dado seja usado como para fornecer à classe alguma informação básica sobre o parâmetro genérico.

Um dos novos recursos do Delphi são Generics. Eles são similares aos templates do C++, mas não totalmente idênticos, por isso não vamos compará-los. São mais parecidos com os Generics do C#, então quem já está familiarizado com o ambiente .NET não sentirá dificuldades em usá-los no Delphi, e ainda ficará feliz em saber que agora podemos desfrutar desse recurso em um compilador de executáveis Win32 nativos.

Generics é um tipo de dado flexível. São tipos parametrizados, ou, mais claramente, parâmetros-tipo, que podem ser definidos em design. Não devemos confundir os parâmetros genéricos com os tipos propriamente ditos. Generics não são tipos, são parâmetros. Assim como os parâmetros ou argumentos de um método são substituídos por um valor, o parâmetro do tipo genérico é substituído por um tipo. Os Generics fornecem uma nova forma de flexibilização dos nossos sistemas, para somar-se às formas que nós já conhecemos, como interfaces, padrões de projeto e RTTI.

Podemos criar métodos com argumentos cujos tipos podem ser indefinidos, assim, podemos trocar o tipo de dado que o método usa cada vez que precisarmos, sem ter a obrigação de criar vários métodos diferentes. Também é possível criar classes que podem virtualmente se conectar com quaisquer outras, porque contém métodos ou propriedades que aceitam dados de qualquer tipo.

Você pode argumentar que sempre fez isso usando Variants quando o tipo de dado era um tipo primitivo, ou um objeto da classe TObject quando o tipo fosse uma classe qualquer. Porém, nesses casos, você era obrigado a usar os comandos da RTTI “is” e “as” para tentar fazer um TypeCast seguro, ou disparar uma exceção caso o tipo não fosse o correto. Nem sempre você conseguiria o tipo correto e isso era causa de erros no seu código.

Outras vezes, para flexibilizar a passagem de parâmetros entre os objetos, porém limitando para que não fosse usado qualquer tipo, nós usaríamos interfaces. Fazendo com que vários objetos implementassem a mesma interface você poderia intercambiá-los à vontade, ainda com a vantagem de acesso a todos os métodos da interface, do AutoComplete em tempo de design, no IDE, e da notificação de algum erro na hora da compilação.

Então, para que Generics?

Generics permitem que o tipo de dado passado seja um tipo primitivo, como Integer ou Double, ou um objeto de uma classe, ou ainda um tipo estruturado como um Record. Além disso, caso você informar o tipo errado, você receberá a mensagem de erro na hora da compilação, e não em tempo de execução, com o programa em produção, causando um Access Violation. E ainda tem a vantagem de ter acesso a todas as informações do objeto em tempo de Design, através do AutoComplete.

Na maioria dos casos usam-se os tipos genéricos para se compor listas de objetos, ou objetos que contêm outros. O Delphi já vem com algumas classes em suas bibliotecas que usam Generics, mais adiante falaremos delas.

Primeiro exemplo com Generics

O primeiro exemplo mostra como é a sintaxe para se criar um método genérico. Vamos criar um novo projeto VCL Forms para Win32 e dar o nome de ConhecendoGenerics.dpr ao projeto e frmGenerics para a Form principal. Criaremos na unit principal duas classes: TPessoa e TOperacao. A classe pessoa terá apenas a propriedade Nome e o método DigaOla. A classe TOperacao terá um método para trocar dois objetos do tipo TPessoa. Vamos adicionar um botão no formulário, chamado btEx1 com o Caption “Exemplo 1” para executar as operações. A classe TPessoa terá um construtor especial onde poderemos inicializar sua propriedade Nome. Então o nome da pessoa poderá ser alterado tanto ao se instanciar o objeto quanto ao mudar sua propriedade Nome. O construtor especial marcamos como overload porque, como a classe é descendente de Tobject, ela já possui um construtor Create implícito, sem argumentos, e nós não queremos substituí-lo, mas queremos ficar com os dois. Vejamos os código da Listagem 1.

Listagem 1. Classes TPessoa e TOperacao


  TPessoa = class
  private
    FNome: string;
  public
    procedure DigaAlo;
    constructor Create(umNome: string);  overload;
    property Nome: string read FNome write FNome;
  end;
   
  TOperacao = class
  public
    procedure Troca<TipoDeDado>(var Esquerda, Direita: TipoDeDado);
  end;
   
  {...}
   
  implementation
   
  { TPessoa }
  constructor TPessoa.Create(umNome: string);
  begin
    FNome := umNome;
  end;
   
  procedure TPessoa.DigaAlo;
  begin
    ShowMessage('Olá, meu nome é ' + FNome);
  end;
   
  { TOperacao }
  procedure TOperacao.Troca<TipoDeDado>(var Esquerda, Direita: TipoDeDado);
  var
    Temp: TipoDeDado;
  begin
    Temp := Esquerda;
    Esquerda := Direita;
    Direita := Temp;
  end;  ... 

Quer ler esse conteúdo completo? Tenha acesso completo