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)