O que são e quais os tipos de Type Aliases #AQuemPossaInteressar
Escrito por Carlos B. Feitoza Filho | |
Categoria: Artigos | |
Categoria Pai: Addicted 2 Delphi! | |
Acessos: 4008 |
Type aliases (apelidos de tipos) são novos nomes para tipos que já existem e este é apenas um dos usos da palavra-chave type no Delphi. Neste rápido artigo vou falar apenas a respeito do uso desta palavra-chave para fornecer apelidos para tipos ou para criar novos tipos por derivação. Para todos os outros usos desta palavra-chave, clique neste link.
Existem duas formas de se definir um apelido para um tipo no Delphi, a primeira delas cria um apelido verdadeiro, ou seja, um simples pseudônimo para um outro tipo. Variáveis deste tipo podem ser utilizadas em qualquer contexto onde variáveis do tipo original poderiam ser usadas.
A segunda forma de definir uma apelido, cria na verdade um novo tipo baseado no tipo original, ou seja, cria uma derivação. Variáveis deste tipo não podem ser utilizadas (diretamente) nos mesmos contextos onde variáveis do tipo original poderiam ser usadas.
type
TInfo = record
Login: String;
Senha: String;
end;
TApelidoParaTInfo = TInfo;
TTipoDerivadoDeTInfo = type TInfo;
No código acima existe a declaração de um record TInfo, que seria o nosso tipo original. Apelidos podem ser criados para novos tipos e também para tipos que o Delphi provê, por exemplo, Integer, String, Float, Double, etc. Aqui, optei por criar um record apenas para deixar claro que isso pode ser feito.
Após a declaração de TInfo foram declarados dois apelidos, o primeiro é um apelido verdadeiro (TApelidoParaTInfo) e o segundo é um tipo derivado de TInfo (TTipoDerivadoDeTInfo), ou seja, ele é exatamente igual a TInfo, porém é incompatível com este por causa da derivação!
procedure EscreveInfo(AInfo: TInfo);
begin
WriteLn(AInfo.Login,AInfo.Senha);
end;
procedure GeraInfo(out AInfo: TInfo);
begin
AInfo.Login := 'abc';
AInfo.Senha := '123';
end;
var
TipoDerivadoDeTInfo: TTipoDerivadoDeTInfo;
ApelidoParaTInfo: TApelidoParaTInfo;
Info: TInfo;
O código acima possui dois procedures com um único parâmetro do tipo TInfo, no segundo caso este parâmetro é de saída. Também há a declaração de 3 variáveis, uma para cada tipo declarado.
O procedure GeraInfo, simplesmente inicializa uma variável do tipo TInfo passada em seu parâmetro e o procedure EscreveInfo, escreve numa janela de console o conteúdo de cada um dos campos de uma variável do tipo TInfo passada em seu parâmetro.
// Types of actual and formal var parameters must be identical
GeraInfo(TipoDerivadoDeTInfo);
// Sucesso!
GeraInfo(ApelidoParaTInfo);
GeraInfo(Info);
// Sucesso!
GeraInfo(TInfo(TipoDerivadoDeTInfo));
GeraInfo(TApelidoParaTInfo(TipoDerivadoDeTInfo));
// Incompatible types: 'TInfo' and 'TTipoDerivadoDeTInfo'
EscreveInfo(TipoDerivadoDeTInfo);
// Sucesso!
EscreveInfo(ApelidoParaTInfo);
EscreveInfo(Info);
EscreveInfo(TInfo(TipoDerivadoDeTInfo));
EscreveInfo(TApelidoParaTInfo(TipoDerivadoDeTInfo));
// Incompatible types: 'TTipoDerivadoDeTInfo' and 'TInfo'
TipoDerivadoDeTInfo := ApelidoParaTInfo;
TipoDerivadoDeTInfo := Info;
// Sucesso!
TipoDerivadoDeTInfo := TTipoDerivadoDeTInfo(ApelidoParaTInfo);
TipoDerivadoDeTInfo := TTipoDerivadoDeTInfo(Info);
// Incompatible types: 'TInfo' and 'TTipoDerivadoDeTInfo'
ApelidoParaTInfo := TipoDerivadoDeTInfo;
Info := TipoDerivadoDeTInfo;
// Sucesso!
ApelidoParaTInfo := TApelidoParaTInfo(TipoDerivadoDeTInfo);
ApelidoParaTInfo := TInfo(TipoDerivadoDeTInfo);
Info := TApelidoParaTInfo(TipoDerivadoDeTInfo);
Info := TInfo(TipoDerivadoDeTInfo);
// Sucesso!
ApelidoParaTInfo := Info;
Info := ApelidoParaTInfo;
Ao observar o trecho de código acima, podemos tirar várias conclusões:
- A linha 2 gera o erro apresentado no comentário porque GeraInfo espera receber um TInfo ou um apelido para TInfo, porém está recebendo um tipo derivado de TInfo, que apesar de ser idêntico a este, é incompatível por conta da derivação;
- As linhas 5 e 6 compilam com sucesso porque GeraInfo está sendo executado com um um apelido para um TInfo e com um TInfo propriamente dito, respectivamente. As variáveis ApelidoParaTInfo e Info são, portanto, indistinguíveis;
- As linhas 9 e 10 compilam com sucesso porque foram realizados typecasts, que convertem TipoDerivadoDeTInfo em TInfo e em TApelidoParaTInfo. Note que TInfo e TApelidoParaTInfo são tão compatíveis que se pode até mesmo utilizar qualquer um destes apelidos para realizar o typecast;
- A linha 13 gera o erro apresentado no comentário porque EscreveInfo espera receber um TInfo ou um apelido para TInfo, porém está recebendo um tipo derivado de TInfo, que apesar de ser idêntico a este, é incompatível por conta da derivação;
- As linhas 16 a 19 compilam com sucesso porque EscreveInfo está recebendo variáveis de tipos compatíveis com seu parâmetro (dois primeiros casos) ou que foram convertidas com um typecast (dois últimos casos);
- As linhas 22 e 23 geram o erro do comentário porque, como foi dito, apesar de variáveis do tipo TTipoDerivadoDeTInfo serem idênticas a variáveis do tipo TInfo (ou um de seus apelidos simples) elas não são compatíveis por conta da derivação;
- As linhas 26 e 27 compilam com sucesso porque foi utilizado typacast;
- As linhas 30 e 31 geram o erro do comentário por motivos que já foram explicados, porém vale ressaltar aqui que, em ambos os casos, a mensagem de erro é a mesma, ou seja, ApelidoParaTInfo, do tipo TApelidoParaTInfo, aparece para o compilador como um TInfo, provando que o primeiro é um simples apelido para o segundo e que eles são indistinguíveis;
- As linhas 34 a 37 compilam com sucesso e mostram que é possível realizar typecasts normalmente para atribuir uma variável de um tipo derivado a uma variável do tipo de um apelido ou mesmo uma variável do tipo original;
- As linhas 40 e 41 apenas confirmam que variáveis do tipo original e de seu apelido são 100% intercambiáveis.
Após analisar as várias assertivas acima você pode estar se perguntando quando se deve usar cada uma destas formas de type aliases:
- Sempre que você precisar de um simples apelido para um tipo de dado conhecido mantendo-o compatível com seu tipo original você deverá usar uma apelido simples. Apesar de isso parecer inútil, não é! Suponha que você queira manter uma hierarquia de units e decida definir uma regra onde UnitA só pode usar UnitB e esta por sua vez só pode usar UnitC, porém em um determinado momento você percebe que vai precisar em UnitA de um tipo que só existe em UnitC. Neste caso a solução seria criar em UnitB um apelido para o tipo existente em UnitC, assim, UnitA poderá usar o alias existente em UnitB, que é 100% compatível com o tipo existente em UnitC
- Caso você precise forçar a utilização de um tipo que você definiu, mantendo uma compatibilidade indireta com um tipo original, você deve utilizar a derivação. Todo tipo derivado possui RTTI exclusivo, o que faz com que ele seja distinguível de seu tipo original e isso é muito útil, por exemplo, no desenvolvimento de componentes (veja a seguir).
Tipos derivados e RTTI
Como foi dito anteriormente uma característica importante do tipo derivado é que ele possui RTTI exclusivo. Isso o torna diferente do tipo original, mais ainda mantém a compatibilidade indireta com este. No desenvolvimento de componentes, por exemplo, muitas vezes é necessário criar um editor de propriedade pra uma propriedade específica. O mecanismo usado pelo Delphi para invocar os editores de propriedade, leva em conta o tipo da propriedade. Se esta propriedade for do tipo String e eu precisar de um editor para ela, por exemplo, para gerar uma string formatada, eu simplesmente executo um duplo clique na propriedade no Object Inspector (ou clico no botão com reticências) para invocar seu editor. O editor é associado a propriedade e seu tipo (no caso String), porém isso não é muito adequado, porque torna o editor "compatível" com qualquer propriedade do mesmo tipo (no caso String). Para impedir que um editor de propriedade que edita uma propriedade do tipo String seja usado com qualquer outra propriedade do mesmo tipo, basta criar um tipo derivado de String e associar o editor de propriedade àquele tipo. Ao fazer isso, o editor de propriedade não poderá ser associado a nenhuma outra propriedade do tipo String, mesmo que ele manipule a mesma informação (uma string). Além disso, quando precisarmos associar uma propriedade String ao editor de propriedade, bastará mudar o tipo desta propriedade para o tipo derivado, assim, a propriedade tirará proveito do editor de propriedade imediatamente, sem quebrar os dados que ela já contenha, pois o tipo derivado continua sendo uma string!
Para provar que os tipos derivados e os apelidos têm RTTI distintos, basta executar as linhas a seguir:
WriteLn('TApelidoParaTInfo é um '
,PTypeInfo(TypeInfo(TApelidoParaTInfo))^.Name
,', que é um '
,GetEnumName(TypeInfo(TTypeKind), Byte(PTypeInfo(TypeInfo(TApelidoParaTInfo))^.Kind)));
WriteLn('TTipoDerivadoDeTInfo é um '
,PTypeInfo(TypeInfo(TTipoDerivadoDeTInfo))^.Name
,', que é um '
,GetEnumName(TypeInfo(TTypeKind), Byte(PTypeInfo(TypeInfo(TTipoDerivadoDeTInfo))^.Kind)));
Ao executar o código acima, a seguinte saída vai ser exibida na janela de console:
TApelidoParaTInfo é um TInfo, que é um tkRecord
TTipoDerivadoDeTInfo é um TTipoDerivadoDeTInfo, que é um tkRecord
Como se pode ver, a informação retornada pelo RTTI para TApelidoParaTInfo diz que ele é na verdade um TInfo. Isso o torna 100% compatível com TInfo e indistinguível deste. Por outro lado o RTTI para TTipoDerivadoDeTInfo indica que ele é ele mesmo, e é por isso que ele é diretamente incompatível com TInfo e pode ser usado para identificar propriedades em componentes.