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!
Desta forma, nós temos o class operator Add, com a seguinte assinatura:
Isso me possibilitaria o código:
var
E se você quisesse que _minhaVariavelficasse à 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;
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
Muito trabalho, não é mesmo? E se fosse codificar apenas assim:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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 _minhaVariavelficasse à 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)
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)
Comentários
Postar um comentário