sábado, 5 de maio de 2007

TypeCast e operadores da RTTI

Um recurso que possibilita extrema flexibilização de módulos implementados.

Considero técnica avançada a utilização de typecast, não pela sintaxe ou aplicabilidade do conceito, mas sim, porque na maioria das vazes que um módulo desenvolvido pretende ser mais abstrato, mais genérico, conseqüentemente, por isso, adaptável a diversas situações, implica num custo para sua construção de um grau de complexidade muito maior. E não são poucas as vezes que na implementação de um módulo com essas características seja imprescindível a utilização de typecast.
Na busca de desenvolver módulos flexíveis, freqüentemente o programador escreve em seu código atribuição de valores, ou recuperação deles, a partir de um conhecimento prévio que ele possui, mas o compilador não. Essa não é uma situação muito segura e podem ocorrer problemas, podem ocorre exceções. Inclusive, permita-me um parêntese, falamos sobre esses problemas no artigo sobre exceções, especificamente, exceções de typecast.
Obter informações sobre a herança dos objetos em tempo de execução, também é feito através dessa técnica. Alias, o grande “x” da questão aqui é justamente é obter informação sobre os objetos em run time. O Delphi possui um “mecanismo” para se obter in formações de tipos em tempo de execução, runtime type information (RTTI).


RTTI

O runtime type information, permite que a IDE possa gerar, automaticamente, informações para o desenvolvedor em disign-time sobre um série de coisas. Por exemplo, quando o desenvolvedor “experimenta” o reflexo da manipulação das propriedades dos componentes visuais em tempo de projeto. Podemos tirar vantagens desse mecanismo também para nossas implementações. Entretanto, o emprego dessa técnica não é nada obvia de como se empregar, visto que, na própria documentação do Delphi não encontramos muita coisa sobre isso.
Quando estamos construindo um form, os componentes que adicionamos a ele (depositamos), são automaticamente declarados no topo da definição da classe do from e as propriedades e eventos desses componentes são listados no Object Inpector. A RTTI, faz com que tudo, da definição de uma classe, que estiver declarado na seção pubished produza esse comportamento na IDE.
Note também que quando alteramos os valores default das propriedades dos objetos, essas alterações são persistidas no arquivo .dfm. Dessa forma em tempo de execução seu form é construído exatamente como vc projetou.


O que é Typecast?

Tratar um valor, ou variável de um tipo como se fosse outro tipo. Como? Ficou doido? Não estou doido não, isso é possível. Veja:


procedure TForm1.Button1Click(Sender: TObject);
const
msg = '%d';
MsgS = '%s';
var
vBoolean: Boolean;
begin

(* recupera o resultado da expressão lógica,
relacionando os correspondentes valores inteiros a cada um dos
caracteres ASCII, “A” e “\”.*)
vBoolean := word('A') < Integer('\');

(*exibe o valor inteiro correspondente ao caracter ‘/’ nas tabela ASCII *)
ShowMessage(Format(msg,[Integer('/')]));

(* Atribui ao caption do form o caracter d tabela ASCII, cujo o valor inteiro é 65 *)
Self.Caption := Format(MsgS, [Char(65)]);

(*Atribui ao caption do Panel1 o valor inteiro correspondente ao booleano “True”/”Verdadeiro”*)
Panel1.Caption := Format(msg,[Cardinal(True)]);

(*exibe a soma do valor inteiro correspondente ao caracter ‘B’, na tabela ASCII,
mais o inteiro correspondente ao booleano “False”. *)

MessageDlg(Format(msg,[Byte(False) + Cardinal('B')]), mtWarning, [mbOK], 0);

(* Atribui ao caption do form o caracter correspondente ao Booleano *)
Self.Caption := Self.Caption +' - ' + Char(vBoolean);

end;


Sinteticamente falando, trata-se de uma forma de “forçar” o compilador a enxergar, considerar, uma variável, um valor, um objeto de um tipo específico como se fosse outro tipo. Entenda “Objeto” como uma instância de uma classe, uma variável que aloca memória dinamicamente para um determinado tipo de classe.

Sintaxe:
No que tange a esse critério, a seguir as formas de sintaxe para fazermos typecast:

A sintaxe para um cast de tipos primitivos é: Tipo(ValorOuVariavel);

Para objetos:

Forma 1 – HardCast (também amplamente referenciado por type cast estilo “C”).

TEdit(Sender).text := ‘Bla bla bla!!! Oba!!!’;
ShowMessage(TEdit(Sender).text);
TEdit(Sender).SetFocus;


Forma 2 - Usando o operador de Typecast “as”

(Sender as TEdit).text := ‘Bla bla bla!!! Oba!!!’;
ShowMessage((Sender as TEdit).text);
(Sender as TEdit).SetFocus;


Usei propositalmente uma variável identificada por “Sender”, visto que, quase todos os eventos dos objetos da VCL possuem um parâmetro que é um ponteiro para o objeto que o chamou (o procedimento) identificado por este nome. Sender é sempre do tipo TObject, portanto, no evento OnClick de qualquer botão, quando ele for executado, o Sender estará apontando para ele.
Contudo, observe o trecho de código acima, Sender é um TObject, ele não possui uma propriedade “Text”. Alias, todas as propriedades que caracterizam um objeto ser um Edit, obrigatoriamente, não são encontradas em TObject. No entanto, para manipular o evento OnMouseMove do Edit, eu tenho como referencia ao objeto que disparou esse evento o parâmetro Sender, cujo o tipo não é TEdit. A través do typecast, eu comunico ao compilador, que embora, no trecho de código, o identificador seja TObject, em tempo de execução um objeto Edit estará alocado naquele endereço de memória.
Não podemos deixar de considerar que o programador pode errar, existem uma infinidade possibilidades na implementação de um programa que torna o typecast exemplificado acima extremamente suscetível a erros. Na verdade, um único e fatal erro. Por exemplo, se por algum motivo no trecho de código acima o identificador Sender não estiver apontando para um Edit. Em última análise, o Sender pode estar apontando para qualquer lugar. Quem nunca viu pérolas como essa nos fontes da vida:

Button1Click(nil);

Nessa situação o Sender esta apontando pra casa do cabula (aonde tem jaca mole). Supondo que a implementação desse evento, “Button1Click”, seja:

// forma 1 - Hardcast
com hardcast
procedure TForm1.Button1Click(Sender: TObject);
begin
TButton(Sender).Left := (Sender as TButton).Left + 20;
TButton(Sender).Caption := 'Andei!!! ... que maravilha!!!';
end;

//forma 2 - Com operador de typecast
procedure TForm1.Button1Click(Sender: TObject);
begin
(Sender as TButton).Left := (Sender as TButton).Left + 20;
(Sender as TButton).Caption := 'Andei!!! ... que maravilha!!!';
end;


O que vc acha que vai acontecer? Se Sender for nulo, não importa qual forma de typecast, vc vai visitar o cabula ... acesso violado de cara. Caso contrário, Sender não é nulo, mas aponta para um tipo incompatível. Veja:


Usando o operador “as”: Na primeira linha, por utilizar o operador “as” ele usa RTTI para verificar se o objeto instanciado na que endereço é de tipo compatível com o que esta proposto pelo programador. Dessa maneira, antes de tentar acessar algo que não exista, ou que seja incompatível, como exemplificado acima, ele executa um teste. Caso não haja compatibilidade é lançada uma exceção de typecast inválido, sem nenhum comprometimento do funcionamento do aplicativo.

Usando o hardcast: por estar sendo feito o “hardcast”, o processador, em run-time, vai acessar o endereço referenciado pelo identificador Sender. Ou seja, a casa do cabula!!! Ele não vai estar em casa, alias, ninguém vai estar em casa. As jacas moles, inevitavelmente, serão acessadas violentamente .... o final da história vc já sabe ... presumo eu, não sei...

Voltando a falar sério, o hardcast não oferece nenhum nível de proteção. Caso haja incompatibilidade de tipos, vc vai levar um "access violation”, que poderá comprometer o funcionamento do aplicativo.

Ora, vc deve estar pensando: Se eu tenho a RTTI para obter dinamicamente informações sobre o tipo do objeto, então eu posso fazer um typecast mais seguro, correto? Mas é claro ... mas é claro. Eu posso usar o outro operador da RTTI, “is”. Com ele antes de fazer o cast eu posso testar se o tipo é compatível com o esperado, desta forma eu consigo um typecast seguro. Consigo eliminar as possibilidades de erro.


procedure TForm1.Button1Click(Sender: TObject);
begin
if (Sender is TButton) then
begin
(Sender as TButton).Left := (Sender as TButton).Left + 20;
(Sender as TButton).Caption := 'Andei!!! ... que maravilha!!!';
end;
end;


Agora, seu cast está seguro, todavia, há que se considerar que, embora não haja erro no código acima, não existe mais vantagem nenhuma em fazer o cast usando o operador “as”. Visto que, o desvio condicional garante que somente tipos compatíveis com TButton sofrerão o cast. Portanto, posso com segurança optar pelo hardcast. Alguns programadores preferem isso, porem, na minha opinião, o ganho que isso pode proporcionar não é representativo. Porque, mesmo que a utilização do operador “as” possa consumir algum recursos do processador, ao consultar a RTTI, frente a performance dos hardwares atuais esse consumo é insignificante.
Vou terminando por aqui .... pode ser que no futuro voltemos a falar sobre esse assunto.
Veja mais exemplos da aplicação de typecast neste artigo.
hum!!! Lembrei de outro.

Nenhum comentário:

Postar um comentário

 
BlogBlogs.Com.Br