Record, operadores e uma mão na roda!

Record é uma das estruturas de dados mais simples, desde o pascal, que foi turbinada na Delphi language: Agora suporta propriedades, métodos e seu controle de visibilidade e… OPERATOR OVERLOADING!

Vamos falar sobre Records

Como eu já disse, são estruturas de dados bem simples. E com a vantagem de que é automaticamente destruída assim que se atingir o fim do escopo. O gerenciamento da memória utilizada também é rápida, uma vez que ela vai direto pro stack — que é bem mais rápida que a heap.

As novidades que a Delphi Language introduziu nos records, além de expandir as possibilidades com esta estrutura, também possibilitou “resolver” um problema comum com os tipos inicializados à partir da stack. Neste setor da memória, as variáveis não são inicializadas. Portanto, você pode ter inteiros com valores aleatórios, variáveis de objeto apontando para um fragmento de memória qualquer e Records com o mesmo valor da última chamada (geralmente acontece quando o tipo de retorno da variável é um record).

Entre as inclusões, agora temos métodos Create e Destroy para records. Esses são chamados implicitamente (você não precisa fazer a chamada deles no código, ainda que possa fazê-lo) quando a variável entra e sai do escopo. Um ótimo espaço para inicializar as variáveis internas do Record e liberar qualquer memória reservada.

Pra mim, records sempre foram uma ótima alternativa de implementação de DTO’s em Delphi. A feature que discuto à seguir ampliou ainda mais as possibilidades:

Overloading operators

Vamos supor que você escreveu o seguinte código:

TCampoValor = Record
  HasValue: boolean;
  Valor: string;
end;

Sempre que você fosse utilizar este record, você teria de inicializar o valor das variáveis internas e então utilizar a notação de ponto para acessar os valores.

Muito trabalho, não é mesmo? E se fosse codificar apenas assim:

procedure Foo;
var
  _minhaVariavel: TCampoRecord;
begin
  _minhaVariavel := 'Um texto qualquer';
  
  if not _minhaVariavel.EstaVazio then
    ShowMessage(_minhaVariavel);
end;

Bem melhor, não é mesmo?

Você pode estar se perguntando: “Mas cara, eu já consigo fazer isso com Helpers de string!”. Sim, você consegue. Mas pense comigo: E se você está querendo fazer atribuições a um banco de dados? Tudo bem, você pode dizer que uma `EmptyStr deve ser gravada como null no banco. Mas será mesmo? Sabemos que um string vazia é diferente de null. E o que você faria com tipos inteiros? Zero seria o mesmo que null?

O sentido dessa implementação está além de atribuir métodos ao tipo string, mas ampliar a possibilidade com os records.

Allen, usuário da embarcadero, escreveu este post entrando em maiores detalhes sobre a implementação de tipos nullable (nuláveis?) para Delphi. Se você quiser desbravar este assunto, é um bom lugar pra começar.

O que permite a mágica do trecho de código acima são justamente os class operators. Esses caras, basicamente, sobrescrevem o modo como o compilador irá interagir os diversos operadores e o record em questão.

Como pensar?

Imagine que a maior parte das operações envolvendo operadores possui sempre um valor a esquerda e um valor a direita. Uma interação (comparação, atribuição, operação matemática) acontece entre esses dois valores, de acordo com o operador especificado;

10 = 5+5
resultado = Esquerda + Direita
result := aLeft + aRight; 

De que outra forma este código poderia ser escrito?

result := aLeft.Add(aRight);

Desta forma, nós temos o class operator Add, com a seguinte assinatura:

(...)
class operator Add(aLeft: TMeuRecord; aRight: Integer): Integer;
(...)
class operator TMeuRecord.Add(aLeft: TMeuRecord; aRight: Integer): Integer;
begin
  result := aLeft + aRight;
end

Isso me possibilitaria o código:

var
  _minhaVariavel: TMeuRecord;
begin
  _minhaVariavel.Valor := 5;
  result := _minhaVariavel + 5;
end;

E se você quisesse que _minhaVariavel ficasse à direita? Você teria que criar um novo overload com a combinação correta. Ou ainda, poderia sobrescrever o comportamento de cast implícito: Implicit(a : type) : resultType;

(...)
class operator Implicit(aVariavel : TMeuRecord): Integer;
(...)
class operator TMeuRecord.Implicit(aVariavel: TMeuRecord): Integer;
begin
  result := AVariavel.Valor;
end;

Desta maneira, sempre que for necessário um cast implícito de TMeuRecord para Integer, este método será chamado.

Como você pode observar, os cast implícito e explícitos ajudam bastante a diminuir o número de combinações necessárias para sobrescrever os operadores.

Outra ajuda muito bem vinda é o uso de Generics. O artigo do Allen, citado acima, dá exemplos de como utilizar Record e Generics e assim implementar os class operator de forma mais dinâmica e flexível.

A coisa começa a complicar um pouco quando você precisa sobrescrever os operadores de comparação — algo que não está descrito no código que Allen propõe.

Eu já tenho testado algumas coisas, mas será assunto para um novo artigo.

Que tal você ir tentando com o que já temos? Deixo pra você uma lista com os overloading operator suportados pela Delphi Language: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Operator_Overloading_(Delphi)

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.