Você está na página 1de 251

Machine Translated by Google

10: propriedades e eventos - 299

Primeiro temos que herdar nossa classe da classe TComponent em vez da classe TOb-ject padrão . Aqui está o
código:

tipo
TDate = classe(TComponent)
...
público
construtor Criar (AOwner: TComponent); sobrecarga; sobrepor;
construtor Criar (Y, M, D: Inteiro); reintroduzir; sobrecarga;

Como você pode ver, a segunda etapa foi adicionar um novo construtor à classe, substituindo o construtor padrão
dos componentes para fornecer um valor inicial adequado. Por haver uma versão sobrecarregada, também
precisamos usar a diretiva reintroduce para ela, para evitar uma mensagem de aviso do compilador. O código do
novo construtor simplesmente define a data para a data de hoje, após chamar o construtor da classe base:

construtor TDate.Create(AOwner: TComponent);


era
Y, D, M: Palavra;
começar
herdado Criar (AOwner);
FDate := Data; // Hoje

Feito isso, precisamos adicionar à unidade que define nossa classe (a unidade Dates do exemplo DateComp ) um
procedimento Register . (Certifique-se de que esse identificador comece com R maiúsculo, caso contrário ele não
será reconhecido.) Isso é necessário para adicionar o componente ao IDE.

Simplesmente declare o procedimento, que não requer parâmetros, na parte de interface da unidade, e então escreva
este código na seção de implementação :

Cadastro de procedimento;
começar
RegistrarComponentes(end; 'Amostra' , [TDate]);

Este código adiciona o novo componente à página Sample da paleta Tools, criando a página, se necessário.

A última etapa é instalar o componente. Para isso precisamos criar um pacote, que é um tipo especial de componentes
de hospedagem de projetos de aplicativos. Tudo o que tem a fazer é:

· Selecione o Arquivo | Novo | Outro menu do IDE, abrindo a caixa de diálogo Novos Itens

· Selecione “Pacote”

· Salve o pacote com um nome (possivelmente na mesma pasta da unidade com o código real do componente)

· No projeto de pacote recém-criado, no painel Gerenciador de Projetos, clique com o botão direito no
Contém o nó para adicionar uma nova unidade ao projeto e selecionar a unidade com o TDate
classe de componente

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

300 - 10: propriedades e eventos

· No Gerenciador de Projetos você pode clicar com o botão direito no nó do pacote e emitir um comando
Build e, após uma compilação bem-sucedida, selecionar o item de menu Instalar, para instalar o
componente no ambiente de desenvolvimento

· Se você começar com o código que acompanha o livro, tudo o que você precisa fazer é o último passo
da sequência acima: Abra o projeto DatePackage da pasta DateComponent e compile e instale-o

Se você criar agora um novo projeto e passar para a paleta de ferramentas, deverá ver o novo componente
em Amostra. Basta começar a digitar seu nome para procurá-lo. Isso será mostrado usando o ícone
padrão dos componentes. Neste ponto você pode colocar o componente em um formulário e começar a
manipular suas propriedades no Object Inspector, como você pode
veja na Figura 10.2. Você também pode manipular o evento OnChange de uma maneira muito mais fácil
do que no último exemplo.

Figura 10.2: As
propriedades do nosso
novo componente TDate no
Object Inspector

Além de tentar construir seu próprio aplicativo de exemplo usando este componente (algo que eu realmente
sugiro que você faça), agora você pode abrir o exemplo DateComponent , que é uma versão atualizada
do componente que construímos passo a passo nas últimas seções. deste capítulo. Esta é basicamente uma
versão simplificada do exemplo DateEvent , porque agora o manipulador de eventos está diretamente
disponível no Object Inspector.

note Se você abrir o exemplo DateCompTest antes de compilar e instalar o componente específico
pacote (o exemplo DatePackage ), o IDE não reconhecerá o componente ao abrir o formulário e exibirá uma mensagem
de erro. Você não será capaz de compilar o programa ou fazê-lo funcionar corretamente até instalar o novo componente.
Observe que a mensagem de erro exibida pelo IDE neste caso oferece duas opções: Cancelar apenas ignora o
componente indefinido, enquanto Ignorar irá removê-lo do código.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 301

Implementando suporte de enumeração em uma classe

No Capítulo 3 vimos como você pode usar um loop for-in como alternativa ao loop for clássico . Nessa
seção, descrevi como você pode usar loops for-in para arrays, strings, conjuntos e alguns outros tipos
de dados do sistema. É possível aplicar tal loop a qualquer classe, desde que defina suporte de
enumeração. Embora o exemplo mais óbvio sejam as classes que contêm listas de elementos, do
ponto de vista técnico esse recurso é bastante
aberto.

Para suportar a enumeração dos elementos de uma classe em Object Pascal você deve
adicionar um método chamado GetEnumerator que retorna uma classe (a classe enumeradora
real) e definir esta classe enumeradora com um método MoveNext e uma propriedade Current – a primeira
para navegar entre os elementos e o segundo para retornar um elemento real. Feito isso (e mostrarei
como em um exemplo real em um segundo), o compilador pode resolver um loop for-in no qual o destino
é nossa classe e os elementos individuais devem ser do mesmo tipo da propriedade Current do
recenseador.

Embora não seja estritamente necessário, parece uma boa ideia implementar a classe de suporte do
enumerador como um tipo aninhado (um recurso da linguagem abordado no Capítulo 7) porque
realmente não faz sentido usar o tipo específico usado para a enumeração por si só.

A classe a seguir, parte do exemplo NumbersEnumerator , armazena um intervalo de números


(uma espécie de coleção abstrata) e permite iterá-los. Isso é possível definindo um enumerador, declarado
como tipo aninhado e retornado pelo GetEnumerator
função:

tipo
TNumbersRange = classe
público
tipo
TNumbersRangeEnum = classe
privado
NPos: Inteiro;
FRange: TNumbersRange;
público
construtor Criar (ARange: TNumbersRange);
função MoveNext: Booleano;
função GetCurrent: Inteiro;
propriedade Atual: leitura inteira GetCurrent;
fim;

privado
FNStart: Inteiro;
FNEnd: Inteiro;
público
função GetEnumerator: TNumbersRangeEnum;
procedimento Set_NEnd (valor const: inteiro);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

302 - 10: propriedades e eventos

procedimento Set_NStart (valor const: inteiro);

propriedade NStart: Leitura inteira FFNStart gravação Set_NStart;


propriedade NEnd: Leitura inteira FNend gravação Set_NEnd;
fim;

O método GetEnumerator cria um objeto do tipo aninhado que armazena informações de status para
iteração dos dados.

Observe como o construtor do enumerador mantém uma referência ao objeto real que está
enumerando (objeto que é passado como parâmetro usando Self) e define a posição inicial bem no
início:

função TNumbersRange.GetEnumerator: TNumbersRangeEnum;


começar
Resultado: = TNumbersRangeEnum.Create (Self);
fim;

construtor TNumbersRange.TNumbersRangeEnum.
Criar(ARange: TNumbersRange);
começar
herdado Criar;
FRange := ARange;
NPos := FRange.NStart - 1;
fim;

note Por que o construtor define o valor inicial como o primeiro valor menos 1, em vez do primeiro valor, como esperado? Acontece
que o código gerado pelo compilador para o loop for in corresponde à criação da enumeração e à execução do código
enquanto MoveNext usa Current. O teste é realizado antes de obter o primeiro valor, pois a lista pode não ter valor. Isso
implica que MoveNext é chamado antes do primeiro elemento ser usado. Em vez de implementar isso com uma lógica
mais complexa, simplesmente defini o valor inicial como um antes do primeiro , de modo que na primeira vez que MoveNext
for chamado, o enumerador será posicionado no primeiro valor.

Finalmente, os métodos enumeradores permitem acessar os dados e fornecem uma maneira de


passar para o próximo valor na lista (ou o próximo elemento dentro do intervalo):

função TNumbersRange.TNumbersRangeEnum.GetCurrent: Inteiro;


começar
Resultado := NPos;
fim;

função TNumbersRange.TNumbersRangeEnum.MoveNext: Booleano;


começar
Inc(NPos);
Resultado:= NPos <= FRange.NEnd;
fim;

Como você pode ver no código acima, o método MoveNext serve a dois propósitos diferentes;
passando para o seguinte elemento da lista e verificando se o enumerador atingiu
o final, caso em que o método retorna False.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 303

Depois de todo esse trabalho, agora você pode usar o loop for-in para iterar pelos valores do objeto
range:
era
ARange: TNumbersRange;
Eu: Inteiro;
começar
ARange := TNumbersRange.Create;
ARange.NStart := 10;
ARange.NEEnd := 23;

para eu em ARange faço


Mostrar(IntToStr(I));

A saída é simplesmente a lista de valores enumerados entre 10 e 23 inclusive:


10
11
12
13
14
15
16
17
18
19
20
21
22
23

note Há muitos casos em que as bibliotecas RTL e VCL definem enumeradores, por exemplo, cada
TComponent pode enumerar o componente que possui. O que falta é a enumeração dos controles filho. Em
Capítulo 12, na seção “Adicionando uma enumeração com um auxiliar de classe”, veremos como você pode criar
um. A razão pela qual o exemplo não está aqui é porque primeiro precisamos discutir os ajudantes de classe.

15 dicas sobre como misturar RAD e OOP


Neste capítulo, abordei propriedades, eventos e a palavra-chave publicada que compõem os
principais recursos da linguagem relacionados ao desenvolvimento rápido de aplicativos (RAD) ou
ao desenvolvimento visual ou à programação orientada a eventos (três termos que se referem ao
mesmo modelo conceitual). Embora este seja um modelo muito poderoso, ele é apoiado
por uma arquitetura OOP sólida. Às vezes, a abordagem RAD pode levar os desenvolvedores a
esquecerem as boas práticas de OOP. Ao mesmo tempo, voltar a escrever código puro e ignorar
a abordagem RAD pode muitas vezes ser contraproducente. Nesta última seção do capítulo eu

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

304 - 10: propriedades e eventos

listei algumas dicas e sugestões sobre como unir as duas abordagens. Outra maneira de descrevê-lo é
considerá-lo uma seção sobre “OOP além do RAD”.

nota O material nesta seção final do capítulo foi publicado originalmente na edição 17 do “The Delphi
Magazine” (julho de 1999) com o título “20 regras para OOP em Delphi”. Agora cortei algumas regras
e reformulei outras, mas a essência permanece.

Dica 1: um formulário é uma classe


Os programadores muitas vezes tratam os formulários como objetos, quando na verdade são classes. A
diferença é que você pode ter vários objetos de formulário baseados na mesma classe de formulário.

O que é confuso é que o IDE cria uma variável global padrão e (dependendo das suas configurações) também
pode criar um objeto de formulário na inicialização para cada classe de formulário que você definir em seu
projeto. Isto é certamente útil para os recém-chegados, mas geralmente é um mau hábito para qualquer
aplicação não trivial.

Claro, é muito importante dar um nome significativo a cada formulário (e sua classe) e a cada unidade.
Infelizmente, os dois nomes devem ser diferentes, mas você pode usar uma convenção para mapear os
dois de maneira consistente (como AboutForm e About.pas).

À medida que você avança nas etapas a seguir, verá os efeitos práticos desse conceito de “um formulário é
uma classe”.

Dica 2: Nomeie os Componentes


Também é importante usar nomes descritivos para componentes e não usar os nomes padrão atribuídos
pelo designer do formulário. A notação mais comum é usar algumas iniciais
letras para o tipo de classe, seguidas da função do componente, como em BtnAddCustomer
ou EditarNome. Na verdade, existem muitas notações semelhantes seguindo esse estilo e não há razão para
dizer que qualquer uma delas é a melhor, depende do seu gosto pessoal.

Dica 3: nomeie eventos


É igualmente importante dar nomes próprios aos métodos de manipulação de eventos. Se você nomear os
componentes corretamente, o nome padrão para um manipulador OnClick , por exemplo, se tornará
BtnAddCustomerClick. Embora possamos adivinhar o que o método faz a partir do
nome do botão, acho melhor usar um nome que descreva o efeito do método, não quando o método é acionado.
Por exemplo, o evento OnClick do botão BtnAddCus-tomer poderia ser denominado AddCustomerToList se
for isso que o método faz.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 305

Isso torna o código mais legível, especialmente quando você chama o manipulador de eventos de outro
método da classe, e ajuda os desenvolvedores a anexar o mesmo método a vários eventos ou a diferentes
componentes; embora eu deva dizer que usar Actions é a escolha preferida para anexar um único evento
a vários elementos da interface do usuário em qualquer programa não trivial.

note Actions e o componente ActionList são recursos arquitetônicos muito interessantes das bibliotecas VCL e Fire-
Monkey UI, oferecendo uma separação conceitual das operações do usuário (e seu status) dos controles da
interface do usuário aos quais estão associados. Ativando o controle, executa a ação. Na verdade, se você
desabilitar logicamente a ação, os elementos da UI associados a essa ação também serão desabilitados.
Este tópico está além do escopo deste livro, mas vale a pena investigar se você usa as bibliotecas VCL ou FMX.

Dica 4: use métodos de formulário


Se os formulários forem classes, seu código será coletado em métodos. Além dos manipuladores de
eventos, que desempenham uma função especial, mas ainda podem ser chamados como outros
métodos, geralmente é útil adicionar métodos personalizados às classes do formulário. Você pode adicionar
métodos que executam ações e operações gerais associadas ao formulário e acessam o status dos
componentes do formulário. É muito melhor adicionar métodos e propriedades públicas a um formulário
do que outras partes do sistema acessarem diretamente seus componentes do formulário.

Dica 5: adicione construtores de formulário


Um formulário secundário criado em tempo de execução pode fornecer outros construtores específicos
além do padrão (herdado da classe TComponent ). Quando precisar de uma inicialização específica,
minha sugestão é sobrecarregar o método Create , adicionando a inicialização necessária
parâmetros, como no seguinte trecho de código:

público
construtor Criar(const AText: string); reintroduzir; sobrecarga;

construtor TFormDialog.Create(const AText: string);


começar
herdado Criar (Aplicativo);
Edit1.Text := AText;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

306 - 10: propriedades e eventos

Dica 6: Evite Variáveis Globais


Variáveis globais (ou seja, variáveis declaradas na parte de interface de uma unidade) devem ser evitadas, pois na maioria
das vezes levam a dificuldades de manutenção do código, bem como a problemas evitáveis.

insetos.

Se você precisar de armazenamento extra de dados para um formulário, por exemplo, adicione alguns campos privados
a ele. Neste caso, cada instância do formulário terá sua própria cópia dos dados.

Você pode usar variáveis de unidade (declaradas na parte de implementação da unidade) para dados compartilhados
entre múltiplas instâncias da classe de formulário, mas é muito melhor usar dados de classe (explicados no Capítulo
12).

Dica 7: Nunca use uma variável de instância em seu


Implementação
Você nunca deve se referir a um objeto específico em um método da classe desse objeto. Por exemplo, nunca faça
referência à variável MyForm criada automaticamente em um método do TMyForm
aula. Se você precisar se referir ao objeto atual, use o identificador próprio . Lembre-se de que na maioria das vezes
isso não é necessário, pois você pode consultar diretamente os métodos e dados do objeto atual. Se você não seguir
esta regra, você terá sérios problemas ao criar múltiplas instâncias da classe (que pode ser um formulário).

Dica 8: raramente use uma variável de formulário


Mesmo no código de outras classes, incluindo outros formulários, tente evitar referências diretas a objetos globais, como
MyForm. É muito melhor declarar variáveis locais ou campos privados para fazer referência a outros formulários, como
FMyForm. Por exemplo, o formulário principal de um programa pode ter um campo privado referente a uma caixa de
diálogo. Obviamente esta regra se torna essencial se você planeja criar múltiplas instâncias do formulário secundário.
Você pode manter uma lista em um array dinâmico do formulário principal, ou simplesmente usar o array Forms do objeto
Tela global para fazer referência a qualquer formulário existente atualmente na aplicação.

Dica 9: Remova a variável global Form1


Na verdade, minha sugestão é remover o objeto de formulário global que é adicionado automaticamente pelo IDE ao
projeto conforme você adiciona um novo formulário a ele, como o Form1. Isto só é possível se você desabilitar a criação
automática desse formulário, algo que sugiro que você

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 307

deveria se livrar de qualquer maneira. Alternativamente, você pode excluir a linha correspondente usada para
criar o formulário a partir da origem do projeto.

Escusado será dizer que, se o formulário não for criado automaticamente, você precisará de algum código em
seu aplicativo para criá-lo e, possivelmente, de alguma outra variável para se referir a ele.

Acho que remover o objeto de formulário global é muito útil para os novatos em Object Pascal, que não
ficarão mais confusos entre a classe e o objeto global. Na verdade, após a remoção do objeto global, qualquer
referência a ele resultará em erro.

Dica 10: adicione propriedades de formulário

Como já discuti na seção “Adicionando propriedades a formulários”, neste capítulo, quando você precisar de
dados para um formulário, adicione um campo privado. Se você precisar acessar esses dados de outras
classes, adicione propriedades ao formulário. Com esta abordagem você poderá alterar o código do
formulário e seus dados (incluindo sua interface de usuário) sem precisar
alterar o código de outros formulários ou classes. Você também deve usar propriedades ou métodos para
inicializar um formulário secundário ou caixa de diálogo e ler seu estado final. A inicialização também pode
ser realizada através de um construtor, conforme já descrevi.

Dica 11: exponha as propriedades do componente

Quando você precisar acessar o status de outro formulário, não deverá consultar diretamente seus componentes.
Isso vincularia o código de outros formulários ou classes à interface do usuário, que é uma das partes de
uma aplicação sujeita à maioria das alterações, mas também evitaria o encapsulamento da lógica que envolve
o gerenciamento desses componentes. Em vez disso, declare uma propriedade de formulário mapeada
para a propriedade do componente: isso é feito com um método Get que lê o status do componente e
um método Set que o grava. Suponha que você agora altere a interface do usuário, substituindo o
componente por outro. Tudo o que você precisa fazer é corrigir os métodos Get e Set relacionados à
propriedade – você não precisará verificar e modificar o código-fonte de todos os formulários e classes que
possam se referir a esse componente.

Dica 12: use as propriedades do array quando necessário

Se precisar lidar com uma série de valores em um formulário, você pode declarar uma propriedade de array.
Caso esta seja uma informação importante para o formulário, você também pode tornar a propriedade
array padrão do formulário, para que possa acessar diretamente seu valor escrevendo Self[3], ou, de outro
formulário, FDataForm[3]. Isso já foi abordado para um caso mais genérico do que formulários na seção
“Usando propriedades indexadas”.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

308 - 10: propriedades e eventos

Dica 13: Iniciando Operações em Propriedades


Lembre-se que uma das vantagens de usar propriedades em vez de acessar dados globais é que você
pode chamar métodos e fazer qualquer operação ao escrever (ou ler) o valor de uma propriedade. Por
exemplo, você pode desenhar diretamente na superfície do formulário, definir os valores de múltiplas
propriedades, chamar métodos especiais, alterar o status de múltiplos componentes ou disparar
um evento, se aplicável.

Outro exemplo relacionado é usar getters de propriedade para implementar a criação atrasada.
Em vez de criar um subobjeto no construtor da classe, você pode criá-lo na primeira vez que for
necessário, para códigos como este:

privado
FBitmap: TBitmap;
público
propriedade Bitmap: TBitmap lê GetBitmap;

função TBitmap.GetBitmap: TBitmap;


começar
se não for atribuído (FBitmap), então
FBitmap := ... // Criar e inicializar
Resultado := FBitmap;
fim;

Dica 14: ocultar componentes


Muitas vezes ouço puristas da OOP reclamando porque os formulários incluem a lista dos componentes
na seção publicada, uma abordagem que não está de acordo com o princípio do encapsulamento. Na
verdade, eles estão apontando uma questão importante, mas a maioria parece não ter consciência de
que a solução está disponível sem reescrever as bibliotecas ou alterar a linguagem. As referências
de componentes adicionadas a um formulário podem ser movidas para a parte privada da declaração
do formulário, de modo que não sejam acessíveis por
outras formas. Desta forma você pode fazer uso obrigatório das propriedades mapeadas para os
componentes (veja a seção acima) para acessar seu status.

Se o IDE colocar todos os componentes na seção publicada, isso se deve à forma como
esses campos estão vinculados aos componentes criados a partir do arquivo de streaming (DFM ou
FMX). Quando você define o nome de um componente, a VCL anexa automaticamente o objeto do
componente à sua referência no formulário. Isso só é possível se a referência for publicada, pois
o sistema de streaming utiliza o RTTI tradicional e alguns TObject
métodos para realizar a operação.

Então o que acontece é que ao mover as referências dos componentes do publicado para o
seção privada você perde esse comportamento automático. Para corrigir o problema, basta torná-lo
manual, adicionando o seguinte código para cada componente no manipulador de eventos OnCreate do
formulário:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

10: propriedades e eventos - 309

Editar1:=FindComponent( 'Editar1' ) como TEdit;

A segunda operação que você deve fazer é registrar as classes dos componentes no sistema, então
que suas informações de RTTI sejam incluídas no programa compilado e disponibilizadas para
o sistema. Isto é necessário apenas uma vez para cada classe de componente e somente se você mover
todas as referências de componentes deste tipo para a seção privada. Você pode adicionar esta
chamada mesmo se não tiver certeza se ela é necessária para seu aplicativo, pois uma chamada extra
ao método Reg-isterClasses para uma classe já registrada não tem efeito. A chamada
RegisterClasses geralmente é adicionada à seção de inicialização da unidade que hospeda o formulário:

RegisterClasses([TEdit]);

Dica 15: use um assistente de formulário OOP


Repetir as duas operações acima para cada componente de cada formulário é certamente enfadonho e
demorado. Para evitar esse fardo excessivo, escrevi um assistente simples que gera as linhas de código
para adicionar ao programa em uma pequena janela. Você precisará executar operações simples de copiar
e colar para cada formulário, pois o assistente não coloca automaticamente o código-fonte na seção
de inicialização da unidade.

Como obter o assistente? Você pode encontrá-lo como parte dos “Cantools Wizards” em:

https://github.com/marcocantu/cantools

Conclusão das dicas


Esta é apenas uma pequena coleção de dicas e sugestões para um modelo de desenvolvimento RAD
e OOP mais equilibrado. É claro que há muito mais neste tópico, que vai muito além do foco deste livro,
que é principalmente na linguagem em si e não na melhor
práticas para arquiteturas de aplicativos.

note Existem alguns livros que cobrem arquitetura de aplicações com Delphi, mas nenhum melhor do que a série
de volumes escritos por Nick Hodges, incluindo “Coding in Delphi”, “More Coding in Delphi” e
“Dependency Injection in Delphi”. Você pode encontrar mais informações sobre esses livros em
http://www.codingindelphi.com/

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

310 - 11: interfaces

11: interfaces

Ao contrário do que acontece em C++ e em algumas outras linguagens, o modelo de herança Object Pascal
não suporta herança múltipla. Isso significa que cada classe pode ter apenas uma única classe base.

A utilidade da herança múltipla é um tema de debate entre os especialistas em OOP. A ausência dessa
construção no Object Pascal pode ser considerada uma desvantagem porque você não tem o poder do C+
+, mas também uma vantagem porque você obtém uma linguagem mais simples e evita alguns dos
problemas que a herança múltipla introduz. Uma maneira de compensar a falta de herança múltipla em
Object Pascal é pelo uso de interfaces, que permitem definir uma classe que implementa múltiplas
abstrações ao mesmo tempo.

note A maioria das linguagens de programação orientadas a objetos atuais não suporta herança múltipla, mas usa interfaces,
incluindo Java e C#. As interfaces fornecem a flexibilidade e o poder de declarar suporte para múltiplas interfaces
implementadas em uma classe, evitando ao mesmo tempo os problemas de herança de múltiplas
implementações. O verdadeiro suporte à herança múltipla permanece principalmente confinado à linguagem C++.
Algumas linguagens dinâmicas orientadas a objetos suportam mix-ins, uma maneira diferente e mais simples
de conseguir algo semelhante à herança múltipla.

Em vez de abrir um debate, assumirei simplesmente que é útil tratar um único objeto a partir de múltiplas
“perspectivas”. Mas antes de construir um exemplo para explicar esse princípio, temos que apresentar o
papel das interfaces no Object Pascal e descobrir como elas funcionam.

De um ponto de vista mais geral, as interfaces suportam um modelo de programação orientada a objetos
ligeiramente diferente das classes. Objetos que implementam interfaces estão sujeitos a

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 311

polimorfismo para cada uma das interfaces que eles suportam, já que você pode invocar qualquer um dos
métodos de interface que o objeto implementa. Comparadas às classes, as interfaces favorecem o encapsulamento
e fornecem uma conexão mais flexível entre as classes do que a herança (já que nenhuma implementação
é herdada).

note As técnicas abordadas neste capítulo e o suporte geral para interfaces foram originalmente adicionados à linguagem Object
Pascal como uma forma de oferecer suporte e implementar o Windows COM (Component Object
Modelo) arquitetura. Mais tarde, o recurso foi estendido para ser totalmente utilizável fora desse cenário, mas alguns
dos elementos COM, como identidade de interface por meio de um ID e suporte de contagem de referência, ainda
permanecem na implementação atual de interfaces do Object Pascal, tornando-os ligeiramente diferentes da maioria
dos outros. línguas.

Usando interfaces
Além de declarar classes abstratas (classes com métodos abstratos), em Object Pascal você também pode escrever
uma classe puramente abstrata; isto é, um tipo de classe com apenas métodos abstratos virtuais. Isso é conseguido
usando a palavra-chave interface para definir um conjunto de tipos de dados que chamamos de interfaces.

Tecnicamente, uma interface não é uma classe, embora possa se assemelhar a uma. Embora uma classe possa ter
uma instância, uma interface não pode. Uma interface pode ser implementada por um ou mais
classes, de modo que as instâncias dessas classes acabem apoiando ou implementando a interface.

No Object Pascal, as interfaces possuem alguns recursos distintos:

· Variáveis de tipo de interface são contadas por referência, diferentemente de variáveis de tipo de classe, oferecendo
uma forma de gerenciamento automático de memória

· Uma classe pode herdar apenas uma classe base, mas pode implementar múltiplas interfaces

· Assim como todas as classes herdam de TObject, todas as interfaces descendem de IInterface,
formando uma hierarquia ortogonal separada

· Por convenção, os nomes das interfaces começam com a letra I, em vez da letra T usada por
a maioria dos outros tipos de dados

note Observação: Originalmente, o tipo de interface base no Object Pascal era chamado de IUnknown, pois é isso que o COM exige.
A interface IUnknown foi posteriormente renomeada como Iinterface para sublinhar o fato de que você pode usar a
interface no Object Pascal mesmo fora do domínio COM e em sistemas operacionais onde o COM não existe.
De qualquer forma, o comportamento real do IInterface ainda é idêntico ao anterior do IUnknown.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

312 - 11: interfaces

Declarando uma interface


Depois de examinar os conceitos básicos, vamos passar para algum código real que deve ajudá-lo a
entender como as interfaces funcionam no Object Pascal. Em termos práticos, uma interface possui uma
definição que se assemelha a uma definição de classe. Esta definição possui uma lista de métodos,
mas esses métodos não são implementados de forma alguma, exatamente como acontece com um
método abstrato em uma classe regular.

A seguir está a definição de uma interface:

tipo
ICanFly = interface
função Voar: string;
fim;

Dado que cada interface herda direta ou indiretamente do tipo de interface base, isso corresponde à
escrita:

tipo
ICanFly = interface(IInterface)
função Voar: string;
fim;

Daqui a pouco mostrarei qual é a implicação de herdar de IInterface e o que isso traz para a mesa. Por
enquanto, perceba que o IInterface possui alguns métodos básicos (novamente, semelhantes ao TObject).

Há um último detalhe relacionado às declarações de interface. Para interfaces, parte da verificação de


tipo é feita dinamicamente e o sistema exige que cada interface tenha um identificador exclusivo, ou
GUID, que você pode gerar no editor Delphi pressionando Ctrl+Shift+G. (Observe que este
atalho pode ser usado em qualquer lugar onde um GUID seja necessário para ser definido em seu
código.)

Este é o código completo da interface:

tipo
ICanFly = interface
'{D7233EF2-B2DA-444A-9B49-09657417ADB7}' ]
[função Fly: string;
fim;

Esta interface e sua implementação (descrita abaixo) estão disponíveis no Intf101


exemplo.

note Embora você possa compilar e usar uma interface sem especificar um GUID, geralmente você desejará gerar o
GUID porque ele é necessário para executar consultas de interface ou dinâmicas como typecasts usando
esse tipo de interface. O objetivo das interfaces é aproveitar as vantagens do tipo bastante estendido de maneira
flexível em tempo de execução e isso depende de as interfaces terem um GUID.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 313

Implementando a Interface
Qualquer classe pode implementar uma ou mais interfaces listando-as após a classe base da qual herda
e fornecendo uma implementação para cada um dos métodos de cada uma das interfaces:

tipo
Avião = classe(..., ICanFly)
função Voar: string;
fim;

função TAirplane.Fly: string;


começar
// Código real
fim;

Quando uma classe implementa uma interface ela deve fornecer uma implementação de todos os métodos
da interface com a mesma assinatura. Neste caso a classe TAirplane deve implementar o método
Fly como uma função retornando uma string. Dado que a interface também herda de uma interface
base (IInterface), uma classe que implementa uma interface deve invariavelmente fornecer todos os
métodos da interface e de todas as suas interfaces base.

Por isso é bastante comum implementar interfaces em classes que herdam de uma classe base que já
implementa os métodos da interface base IInterface . A biblioteca de tempo de execução Object Pascal já
fornece algumas classes base para implementar o comportamento básico. A mais simples é a classe
TInterfacedObject , então o código acima poderia se tornar:

tipo
TAirplane = classe(TInterfacedObject, ICanFly)
função Voar: string;
fim;

note Ao implementar uma interface, você pode usar um método estático ou virtual. Se você planeja substituir os métodos em uma
classe herdada, faz sentido usar métodos virtuais. Existe uma abordagem alternativa, porém, que é especificar que a
classe base também herda da mesma interface e então substitui o método dessa interface. Costumo preferir declarar o
método que implementa métodos de interface como métodos virtuais quando necessário.

Agora que definimos uma interface e uma classe que a implementa, podemos criar um objeto desta
classe. Podemos tratar isso como uma aula normal, escrevendo:

era
Avião1: Avião;
começar
Avião1 := TAirplane.Create;
tentar
Avião1.Fly;
finalmente

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

314 - 11: interfaces

Avião1.Grátis;
fim;
fim;

Neste caso estamos ignorando o fato de que a classe implementa uma interface. A diferença é que agora
também podemos declarar uma variável do tipo interface. Usar uma variável de tipo de interface ativa
automaticamente o modelo de memória de referência, para que possamos pular o bloco try-finally :

era
Folheto1: ICanFly;
começar
Flyer1 := TAirplane.Create;
Folheto1.Fly;
fim;

Existem algumas considerações relevantes sobre a primeira linha deste trecho de código aparentemente
simples, também parte do exemplo Intf101 . Primeiro, assim que você atribui um objeto a uma variável de
interface, o tempo de execução verifica automaticamente se o objeto implementa essa interface,
usando uma versão especial do operador as . Você pode expressar explicitamente esta operação
escrevendo a mesma linha de código da seguinte maneira:

Flyer1 := TAirplane.Create como ICanFly;

Segundo, quer usemos a atribuição direta ou a instrução as , o tempo de execução faz uma coisa
extra: chama o método _AddRef do objeto, aumentando sua contagem de referências. Isso é
feito chamando o método que nosso objeto herda da classe base TIn-terfacedObject.

Ao mesmo tempo, assim que a variável Flyer1 sai do escopo (ou seja, ao executar a instrução final ), o
tempo de execução do Delphi chama o método _Release , que diminui a contagem de referências,
verifica se a contagem de referências é zero, e, neste caso, destrói o objeto. É por isso que no código
acima não há necessidade de liberar manualmente o objeto que criamos no código e não há necessidade
de escrever um bloco try-finally .

note Embora o código-fonte acima não tenha nenhum bloco try-finally , o compilador adicionará automaticamente ao
método um bloco try-finally implícito com a chamada implícita para _Release. Isso ocorre em muitos casos
no Object Pascal: basicamente toda vez que um método tem um ou mais tipos gerenciados (como
strings, interface ou arrays dinâmicos), há um bloco try-finally automático e implícito adicionado pelo
compilador.

Interfaces e contagem de referências


Como vimos no código acima, os objetos Object Pascal referenciados por variáveis de interface são
contados por referência (a menos que a variável do tipo de interface esteja marcada como fraca ou

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 315

inseguro, conforme explicado posteriormente). Vimos também que eles podem ser automaticamente
desalocados quando nenhuma variável de interface se refere mais a eles.

O que é importante reconhecer é que, embora haja alguma mágica do compilador envolvida (as chamadas
_AddRef e _Release ocultas ), o mecanismo real de contagem de referências está sujeito a uma
implementação específica fornecida pelo desenvolvedor ou pela biblioteca de tempo de execução.
No último exemplo, a contagem de referências está realmente em jogo, por causa do código nos
métodos da classe TInterfacedObject (aqui listado em uma versão ligeiramente simplificada):

função TInterfacedObject._AddRef: Inteiro;


começar
Resultado: = AtomicIncrement (FRefCount);
fim;

função TInterfacedObject._Release: Inteiro;


começar
Resultado:= AtomicDecrement(FRefCount);
se Resultado = 0 então
começar
Destruir;
fim;
fim;

Agora considere uma classe base diferente que implementa IInterface e que também é encontrada no
RTL, TNoRefCountObject. Esta classe basicamente desativa o mecanismo real de contagem de
referência:

função TNoRefCountObject._AddRef: Inteiro;


começar
Resultado:= -1;
fim;

função TNoRefCountObject._Release: Inteiro;


começar
Resultado:= -1;
fim;

note A nova classe TNoRefCountObject , que define objetos que ignoram o mecanismo de contagem de
referência, foi introduzida no Delphi 11 para substituir o nome estranho TSingletonImplementation
classe (definida na unidade Generics.Defaults ). A antiga classe TSingletonImplementation ainda existe como
um alias da classe TNoRefCountObject mais recente . O código das duas classes é basicamente idêntico.
O principal motivo da mudança foi que o nome da classe original era um nome impróprio, visto que não
tinha nada a ver com o padrão singleton. Veremos um exemplo desse padrão no próximo capítulo.

Embora TNoRefCountObject não seja comumente usado, existe outra classe que implementa
interfaces e desabilita o mecanismo de contagem de referência, apenas porque tem seu
próprio modelo de gerenciamento de memória, e essa é a classe TComponent .

Se você deseja ter um componente personalizado que implemente uma interface, não precisa se
preocupar com contagem de referências e gerenciamento de memória. Veremos um exemplo de

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

316 - 11: interfaces

componente personalizado implementando uma interface na seção “Implementando Padrões com


Interfaces” no final deste capítulo.

Erros na mixagem de referências


Ao usar objetos, você geralmente deve acessá-los apenas com variáveis de objeto ou apenas com
variáveis de interface. A mistura das duas abordagens quebra o esquema de contagem de referência
fornecido pelo Object Pascal e pode causar erros de memória que são extremamente difíceis de rastrear.
Na prática, se você decidiu usar interfaces, provavelmente deveria usar exclusivamente variáveis
baseadas em interface.

Aqui está um exemplo entre muitos casos semelhantes possíveis. Suponha que você tenha uma interface,
uma classe que o implementa e um procedimento global tomando a interface como parâmetro:

tipo
IMyInterface = interface
'{F7BEADFD-ED10-4048-BB0C-5B232CF3F272}' ]
[Mostrar procedimento;
fim;

TMyIntfObject = classe(TInterfacedObject, IMyInterface)


público
procedimento Mostrar;
fim;

procedimento ShowThat(AnIntf: IMyInterface);


começar
AnIntf.Show;
fim;

O código parece bastante trivial e está 100% correto. O que pode estar errado é a forma como você
chama o procedimento (esse código faz parte do exemplo IntfError ):

procedimento TForm1.BtnMixClick(Remetente: TObject);


era
AnObj: TMyIntfObject;
começar
AnObj := TMyIntfObject.Create;
tentar
MostrarIsso(AnObj);
finalmente
AnObj.Free;
fim;
fim;

O que acontece neste código é que estou passando um objeto simples para uma função que espera
uma interface. Dado que o objeto suporta a interface, o compilador não tem problemas em fazer a
chamada. O problema está na forma como a memória é gerenciada.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 317

Inicialmente, o objeto possui contagem de referência zero, pois não há interface referente a ele.
Ao entrar no procedimento ShowThat , a contagem de referência aumenta para 1. Tudo bem e a chamada
é realizada. Agora, ao sair do procedimento, a contagem de referência diminui e vai para zero,
então o objeto é destruído. Em outras palavras, o AnObj será destruído quando você o passar para um
procedimento, o que é realmente bastante estranho. Se você executar este código, ele falhará com um
erro de memória.

Poderia haver múltiplas soluções. Você poderia aumentar artificialmente a contagem de referência e usar
outros truques de baixo nível. Mas a solução real é não misturar interfaces e referências de objetos e
usar apenas interfaces para se referir a um objeto (este código é usado novamente
do exemplo IntfError ):

procedimento TForm1.BtnIntfOnlyClick(Remetente: TObject);


era
AnIntf: IMyInterface;
começar
AnIntf := TMyIntfObject.Create;
MostrarIsso(AnIntf);
fim;

Neste caso específico a solução está próxima, mas em muitas outras circunstâncias é muito difícil
encontrar o código correto. Novamente, a regra geral é evitar misturar referências de tipos diferentes.
Mas continue lendo a próxima seção para algumas abordagens alternativas recentes.

Referências de interface fracas e inseguras

A partir do Delphi 10.1 Berlin, a linguagem Object Pascal oferece algumas melhorias no gerenciamento de
referências de interface. Na verdade, a linguagem oferece diferentes tipos de referências:

· Referências regulares aumentam e diminuem a contagem de referências do objeto quando


atribuídas e liberadas, eventualmente liberando o objeto quando a contagem de referências
chega a zero

· Referências fracas (marcadas com o modificador [weak] ) não aumentam a contagem de referências
do objeto ao qual se referem. Essas referências são totalmente gerenciadas, portanto, são
automaticamente definidas como nulas se o objeto referenciado for destruído.
· Referências inseguras (marcadas com o modificador [inseguro] ) não aumentam a contagem de
referências do objeto ao qual se referem e não são gerenciadas – algo não muito diferente de um
ponteiro básico.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

318 - 11: interfaces

nota O uso de referências fracas e inseguras foi originalmente introduzido como parte do suporte ao gerenciamento de
memória ARC para plataformas móveis. Como o ARC está descontinuado, esse recurso permanece disponível apenas para
referências de interface.

Nos cenários comuns em que a contagem de referências está ativa, você pode ter um código como o
seguinte, que depende da contagem de referências para descartar o objeto temporário:

procedimento TForm3.Button2Click(Remetente: TObject);


era
OneIntf: ISimpleInterface;
começar
OneIntf := TObjectOne.Create;
OneIntf.DoSomething;
fim;

E se o objeto tiver uma implementação de contagem de referência padrão e você quiser criar uma
referência de interface que seja mantida fora da contagem total de referências? Agora você pode
conseguir isso adicionando o atributo [unsafe] à declaração da variável de interface, alterando o
código acima para:

procedimento TForm3.Button2Click(Remetente: TObject);


era
[inseguro] OneIntf: ISimpleInterface;
começar
OneIntf := TObjectOne.Create;
OneIntf.DoSomething;
fim;

Não que isso seja uma boa ideia, pois o código acima causaria vazamento de memória. Ao desabilitar
a contagem de referência, quando a variável sai do escopo nada acontece. Há
alguns cenários em que isso é benéfico, pois você ainda pode usar interfaces e não acionar a referência
extra. Em outras palavras, uma referência insegura é tratada como um ponteiro, sem suporte extra
do compilador.

Agora, antes de pensar em usar o atributo unsafe para ter uma referência sem aumentar a
contagem, considere que, na maioria dos casos, existe outra opção melhor: o uso de referências
fracas. Referências fracas também evitam o aumento da contagem de referências, mas são
gerenciadas. Isso significa que o sistema monitora as referências fracas e, caso o objeto real seja
excluído, ele definirá a referência fraca como nula. Em vez disso, com uma referência insegura, você
não tem como saber o status do objeto de destino (um cenário chamado referência pendente).

Em quais cenários as referências fracas são úteis? Um caso clássico é o de dois objetos com
referências cruzadas. Nesse caso, de fato, o objeto aumentaria artificialmente a contagem de
referências dos outros objetos, e eles basicamente permaneceriam na memória para sempre (com
a contagem de referências definida como 1), mesmo quando se tornassem inacessíveis.

Como exemplo, considere a seguinte interface aceitando uma referência a outra interface do mesmo
tipo e uma classe que a implementa com uma referência interna:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 319

tipo
ISimpleInterface = procedimento de
interface DoSomething;
procedimento AddObjectRef(Simple: ISimpleInterface); fim;

TObjectOne = classe(TInterfacedObject, ISimpleInterface) privado

OutroObj: ISimpleInterface; procedimento


público
DoSomething; procedimento
AddObjectRef(Simple: ISimpleInterface); fim;

Se você criar dois objetos e fazer referência cruzada a eles, ocorrerá um vazamento de memória:

era
Um, Dois: ISimpleInterface; começar
um :=
TObjectOne.Create; Dois :=
TObjectOne.Create;
One.AddObjectRef(Dois);
Dois.AddObjectRef(Um);

Agora a solução disponível no Delphi é marcar o campo privado AnotherObj como uma referência de
interface fraca:

privado
[fraco] AnotherObj: ISimpleInterface;

Com essa alteração, a contagem de referências não é modificada ao passar o objeto como parâmetro
para a chamada AddObjectRef , ela fica em 1 e volta a zero quando as variáveis saem do escopo, liberando
os objetos da memória.

Agora, existem muitos outros casos em que esse recurso se torna útil e há alguma complexidade real
na implementação subjacente. É um ótimo recurso, mas exige algum esforço para ser totalmente dominado.
Além disso, tem algum custo de tempo de execução, pois as referências fracas são gerenciadas
(enquanto as inseguras não são).

Para obter informações adicionais sobre referências fracas para interfaces e como elas funcionam, você
pode consultar a seção “Gerenciamento de memória e interfaces” no Capítulo 13, “Objetos e memória”.

Técnicas Avançadas de Interface


Para nos aprofundarmos nas capacidades das interfaces, antes de olharmos para os cenários de uso do
mundo real, é importante cobrir alguns de seus recursos técnicos mais avançados, como

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

320 - 11: interfaces

como implementar múltiplas interfaces ou como implementar um método de interface com um método com um
nome diferente (no caso de conflito de nome).

Outro recurso importante que as interfaces possuem são as propriedades. Para demonstrar todos esses recursos
mais avançados relacionados às interfaces, escrevi o exemplo IntfDemo .

Propriedades da interface
O código nesta seção é baseado em duas interfaces diferentes, IWalker e IJumper, ambas
dos quais definem alguns métodos e uma propriedade.

Uma propriedade de interface é apenas um nome mapeado para um método de leitura e gravação . Ao contrário
de uma classe, você não pode mapear uma propriedade de interface para um campo, simplesmente porque uma
interface não pode ter nenhum código associado a ela.

Estas são as definições reais da interface:

IWalker = interface
{0876F200-AAD3-11D2-8551-CCA30C584521}' ]
['função Caminhada: string;
função Executar: string;
procedimento SetPos(Valor: Inteiro);
função GetPos: Inteiro;

posição da propriedade: leitura inteira GetPos gravação SetPos;


fim;

IJumper = interface
{0876F201-AAD3-11D2-8551-CCA30C584521}' ]
['função Salto: string;
função Caminhada: string;
procedimento SetPos(Valor: Inteiro);
função GetPos: Inteiro;

posição da propriedade: leitura inteira GetPos gravação SetPos;


fim;

Ao implementar uma interface com uma propriedade, tudo o que você precisa implementar são os métodos de
acesso reais:

TRunner = classe(TInterfacedObject, IWalker)


privado
FPos: Inteiro;
público
função Caminhada: string;
função Executar: string;
procedimento SetPos(Valor: Inteiro);
função GetPos: Inteiro;
fim;

O código de implementação não é complexo (você pode encontrá-lo no exemplo IntfDemo ), com métodos
computando a nova posição e exibindo o que está sendo executado:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 321

função TRunner.Run: string;


começar
Inc(FPos, 2);
Resultado:=FPos.ToString + end; ': Correr' ;

O código de demonstração usando a interface IWalker e sua implementação TRunner é este:


era
Intf: IWalker;
começar
Intf := TRunner.Create;
Posição Intf:= 0;
Mostrar(Intf.Walk);
Mostrar(Intf.Run);
Mostrar(Intf.Run);
fim;

O resultado não deve surpreender:


1: Caminhada
3: Corre
5: Corre

Delegação de interface
De forma semelhante, posso definir uma classe simples implementando a interface IJumper :

TJumperImpl = classe(TAggregatedObject, IJumper)


privado
FPos: Inteiro;
público
função Salto: string;
função Caminhar: string;
procedimento SetPos(Valor: Inteiro);
função GetPos: Inteiro;
fim;

Esta implementação difere da anterior pela utilização de uma classe base específica, TAggregatedObject. Esta é uma classe
de propósito especial para a definição dos objetos usados internamente para suportar uma interface, com a sintaxe que
mostrarei daqui a pouco.

note A classe TAggregatedObject é outra implementação de IInterface definida na unidade System .


Comparado ao TInterfacedObject possui diferenças na implementação da contagem de referências
(basicamente delegando toda a contagem de referências ao container ou controlador) e na
implementação da consulta de interface, caso o container suporte múltiplas interfaces.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

322 - 11: interfaces

Vou usá-lo de uma maneira diferente. Na classe a seguir, TMyJumper, não quero repetir a
implementação da interface IJumper com métodos semelhantes. Em vez disso, quero delegar a
implementação dessa interface para uma classe que já a implementa. Isto não pode ser feito
através de herança (já que não podemos ter duas classes base); em vez disso, você pode usar
um recurso específico da linguagem: delegação de interface. A classe a seguir implementa uma
interface referindo-se a um objeto de implementação com uma propriedade, em vez de
implementar os métodos reais da interface:

TMyJumper = classe(TInterfacedObject, IJumper)


privado
FJumpImpl: TJumperImpl;
público
construtor Criar;
destruidor Destruir; sobrepor;
propriedade Jumper: TJumperImpl lê FJumpImpl implementa IJumper;
fim;

Esta declaração indica que a interface IJumper está implementada para o TMyJumper
classe pelo campo FJumpImpl . Este campo, é claro, deve implementar todos os métodos da
interface. Para fazer isso funcionar, você precisa criar um objeto adequado para o campo quando
um objeto TMyJumper é criado (o parâmetro construtor é exigido pela classe base
TAggregatedObject ):

construtor TMyJumper.Create;
começar
FJumpImpl := TJumperImpl.Create(Self);
fim;

A classe também possui um destruidor para liberar o objeto interno, que é referenciado por um
campo regular e não por uma interface (já que a contagem de referências não funcionará neste cenário).
Este exemplo é simples, mas em geral as coisas ficam mais complexas à medida que você começa
a modificar alguns dos métodos ou adicionar outros métodos que ainda operam nos dados do objeto
FJumpImpl interno . O conceito geral aqui é que você pode reutilizar a implementação de uma
interface em múltiplas classes. O código que utiliza esta interface implementada indiretamente
é idêntico ao código padrão que se escreveria:

procedimento TForm1.Button2Click(Remetente: TObject);


era
Intf: Saltador;
começar
Intf := TMyJumper.Create;
Posição Intf:= 0;
Mostrar(Intf.Walk);
Mostrar(Intf.Jump);
Mostrar(Intf.Walk);
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 323

Várias interfaces e aliases de métodos


Outra característica muito importante das interfaces é a capacidade de uma classe
implementar mais de uma. Isso é demonstrado pela classe TAthlete a seguir , que implementa
as interfaces IWalker e IJumper :

TAthlete = classe(TInterfacedObject, IWalker, IJumper) private FJumpImpl:

TJumperImpl; construtor público Criar;


destruidor
Destruir; sobrepor; função
Executar: string; virtual; função Walk1: string;
virtual; função IWalker.Walk = Caminhada1;
procedimento SetPos(Valor: Inteiro); função
GetPos: Inteiro;

propriedade Jumper: TJumperImpl lê FJumpImpl implementa IJumper; fim;

Uma das interfaces é implementada diretamente, enquanto a outra é delegada ao objeto


interno FJumpImpl , exatamente como fiz no exemplo anterior.
Agora temos um problema. Ambas as interfaces que queremos implementar possuem um
método Walk , com a mesma assinatura, então como implementar ambas em nossa classe?
Como a linguagem suporta conflitos de nomes de métodos, no caso de múltiplas interfaces? A
solução é dar ao método um nome diferente e mapeá-lo para o método de interface específico
usando-o como prefixo, com a instrução:

função IWalker.Walk = Caminhada1;

Esta declaração indica que a classe implementa o método Walk da interface IWalker com
um método chamado Walk1 (em vez de um método com o mesmo nome).
Por fim, na implementação de todos os métodos desta classe, precisamos nos referir à
propriedade Position do objeto interno FJumpImpl .
Ao declarar uma nova implementação para a propriedade Position , teremos duas posições
para um único atleta, uma situação bastante estranha. Aqui estão alguns exemplos:

função TAthlete.GetPos: Inteiro; resultado inicial: =

FJumpImpl.Position; fim;

função Tathlete.Run: string; começar

FJumpImpl.Position := FJumpImpl.Position + 2; Resultado:=


IntToStr(FJumpImpl.Position) + end; ': Correr' ;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

324 - 11: interfaces

Como podemos criar uma interface para este objeto TAthlete e referir-nos a ambas as operações nas
interfaces IWalker e IJumper ? Bem, não podemos fazer isso exatamente, porque não existe uma
interface base que possamos usar. As interfaces permitem uma verificação e conversão de tipo mais
dinâmica, portanto, o que podemos fazer é converter uma interface em uma diferente, como
desde que o objeto ao qual nos referimos suporte ambas as interfaces, algo que o compilador poderá
descobrir apenas em tempo de execução. Este é o código para esse cenário:

procedimento TForm1.Button3Click(Remetente: TObject);


era
Intf: IWalker;
começar
Intf := TAthlete.Create;
Posição Intf:= 0;
Mostrar(Intf.Walk);
Mostrar(Intf.Run);
Show((Intf como IJumper).Jump);
fim;

Claro, poderíamos ter escolhido qualquer uma das duas interfaces e convertido para a outra. Usar um
as cast é uma forma de fazer uma conversão em tempo de execução, mas há mais opções quando você
está lidando com interfaces, como veremos na próxima seção.

Polimorfismo de Interface
Na seção anterior vimos como você pode definir múltiplas interfaces e ter um
classe implementa dois deles. Claro, isso poderia ser estendido a qualquer número. Você também
pode criar uma hierarquia de interfaces, pois uma interface pode herdar de outra:

ITripleJumper = interface(IJumper)
'{0876F202-AAD3-11D2-8551-CCA30C584521}' ]
[função TripleJump: string;
fim;

Este novo tipo de interface possui todos os métodos (e propriedades) de seu tipo de interface base
e adiciona um novo método. É claro que existem regras de compatibilidade de interface, tanto quanto
o mesmo que para as aulas.

O que quero focar nesta seção, porém, é um tópico um pouco diferente, que é o polimorfismo
baseado em interface. Dado um objeto de classe base geral, podemos invocar um método virtual e ter
certeza de que a versão correta será chamada. O mesmo pode acontecer com interfaces. Com
interfaces, entretanto, podemos ir um passo além e muitas vezes temos código dinâmico que consulta
uma interface. Dado que o relacionamento objeto-interface pode ser
bastante complexo (um objeto pode implementar múltiplas interfaces e indiretamente também
implementar suas interfaces base) é importante ter uma ideia melhor do que é possível neste cenário.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 325

Como ponto de partida, vamos supor que temos uma referência IInterface genérica. Como sabemos se ele
suporta uma interface específica? Na verdade, existem várias técnicas, que são ligeiramente diferentes de
suas contrapartes de classe:

· Use uma instrução is para teste (e possivelmente uma conversão as para a conversão seguinte). Isto
pode ser usado para verificar se um objeto suporta uma interface, mas não se um objeto
referenciado com uma interface também suporta outra (ou seja, você não pode aplicar is a interfaces).
Observe que, em qualquer caso, a conversão as é necessária, pois uma conversão direta para um tipo
de interface quase invariavelmente resultará em erro.

· Chame a função de suporte global , usando uma de suas muitas versões sobrecarregadas. Você pode
passe para esta função o objeto ou a interface a ser testada, a interface alvo (usando o
GUID ou o nome do tipo), e você também pode passar uma variável de interface onde a interface
real será armazenada, se a função for bem-sucedida.

· Chamar diretamente o método QueryInterface da interface base IInterface , que é um nível um pouco
inferior, sempre requer uma variável de tipo de interface como resultado extra e usa um valor HRESULT
numérico em vez de um resultado booleano.

Aqui está um trecho retirado do mesmo exemplo do IntfDemo mostrando como você pode aplicar as duas
últimas técnicas a uma variável IInterface genérica:

procedimento TForm1.Button4Click(Remetente: TObject);


era
Intf: IInterface;
WalkIntf: IWalker;
começar
Intf := TAthlete.Create;
se Suporta (Intf, IWalker, WalkIntf) então
Mostrar(WalkIntf.Walk);

se Intf.QueryInterface(IWalker, WalkIntf) = S_OK então


Mostrar(WalkIntf.Walk);
fim;

Eu recomendo totalmente o uso de uma das versões sobrecarregadas da função Supports , em comparação
com a chamada QueryInterface . Em última análise, o Supports irá chamá-lo, mas oferece opções mais
simples e de nível superior.

Outro caso em que você deseja usar polimorfismo com interfaces é quando você tem uma matriz de
interfaces de um tipo de interface de nível superior (mas também possivelmente uma matriz de objetos,
alguns dos quais podem suportar a interface fornecida).

Extraindo objetos de referências de interface


Foi o caso de muitas versões do Object Pascal que, quando você atribuiu um objeto a uma variável de
interface, não havia como acessar o objeto original. Às vezes,

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

326 - 11: interfaces

os desenvolvedores adicionariam um método GetObject às suas interfaces para realizar a operação,


mas esse era um design bastante estranho.

Na linguagem atual, você pode converter referências de interface de volta ao objeto original ao qual foram
atribuídas. Existem três operações separadas que você pode usar:

· Você pode escrever um teste is para verificar se um objeto de um determinado tipo pode realmente ser extraído
da referência da interface:

IntfVar é TMyObject

· Você pode escrever um as cast para realizar o type cast, gerando uma exceção no caso de um
erro:

IntfVar como TMyObject.

· Você pode escrever um hard type cast para realizar a mesma conversão, retornando nulo
ponteiro em caso de erro:

TMyObject(IntfVar)

note Em todos os casos, a operação de conversão de tipo funciona somente se a interface foi originalmente obtida de um
objeto Object Pascal e não de um servidor COM. Observe também que você pode não apenas converter para a classe
exata do objeto original, mas também para uma de suas classes base (seguindo as regras de compatibilidade de tipos).

Como exemplo, considere ter a seguinte interface simples e classe de implementação (parte do exemplo
ObjFromIntf ):

tipo
ITestIntf = interface(IInterface)
'{2A77A244-DC85-46BE-B98E-A9392EF2A7A7}' ]
[procedimento DoSomething;
fim;

TTestImpl = classe(TInterfacedObject, ITestIntf)


público
procedimento FaçaAlgo;
procedimento DoSomethingElse; // Não em o interface
destruidor Destruir; sobrepor;
fim;

Com essas definições agora você pode definir uma variável de interface, atribuir um objeto a ela e usá-la também
para invocar o método que não está na interface, com a nova conversão:

era
Intf: ITestIntf;
começar
Intf := TTestImpl.Create;
Intf.DoAlguma coisa;
(Intf como TTestImpl).DoSomethingElse;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 327

Você também pode escrever o código da seguinte maneira, usando um teste is e uma conversão direta, e você
sempre pode converter para uma classe base da classe real do objeto:

era
Intf: ITestIntf;
Original: TObject;
começar
Intf := TTestImpl.Create;
Intf.DoAlguma coisa;
se Intf for TObject então
Original := TObject(Intf);
(Original como TTestImpl).DoSomethingElse;

Considerando que uma conversão direta retorna nulo se não for bem-sucedida, você também pode escrever
o código da seguinte maneira (sem o teste is ):

Original := TObject(Intf);
se atribuído (original) então
(Original como TTestImpl).DoSomethingElse;

Observe que atribuir o objeto extraído da interface a uma variável expõe você a problemas de contagem de
referências: quando a interface é definida como nula ou sai do escopo, o objeto é realmente excluído e a
variável referente a ele se tornará inválida. Você encontrará o código destacando o problema no manipulador de
eventos BtnRefCountIssueClick do exemplo.

Implementando um padrão adaptador com


Interfaces
Como exemplo real do uso de interfaces, adicionei a este capítulo uma seção que aborda o padrão do
adaptador. Resumindo, o padrão adaptador é usado para converter a interface de uma classe em outra
esperada pelo usuário da classe. Isso permite usar uma classe existente dentro de uma estrutura que requer
uma interface definida.

O padrão pode ser implementado criando uma nova hierarquia de classes que faça o mapeamento ou
estendendo as classes existentes para que exponham uma nova interface. Isso pode ser feito por herança
múltipla (nas linguagens que a suportam) ou usando interfaces.
Neste último caso, que é o que usarei aqui, uma nova classe herdada implementará a interface dada e
mapeará para seu método o comportamento existente.

No cenário específico, o adaptador fornece uma interface comum para consultar valores de vários componentes,
que possuem interfaces inconsistentes (como geralmente acontece nas bibliotecas de UI). Esta é a interface,
chamada ITextAndValue porque permite

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

328 - 11: interfaces

acessando o status de um componente obtendo uma descrição textual ou numérica


um:

tipo
ITextAndValue = interface
'[51018CF1-OD3C-488E-81B0-0470B09013EB]'
procedimento SetText(const Valor: string);
procedimento SetValue (valor const: inteiro);
função GetText: string;
função GetValue: Inteiro;

propriedade Texto: string lida GetText escreve SetText;


valor da propriedade: leitura inteira GetValue gravação SetValue;
fim;

O próximo passo é criar uma nova subclasse para cada um dos componentes que queremos usar com
a interface. Por exemplo, poderíamos escrever:

tipo
TAdapterLabel = classe(TLabel, ITextAndValue)
protegido
procedimento SetText(const Valor: string);
procedimento SetValue (valor const: inteiro);
função GetText: string;
função GetValue: Inteiro;
fim;

A implementação destes quatro métodos é bastante simples, pois eles podem ser mapeados para a
propriedade Text realizando uma conversão de tipo caso o valor (ou o texto) seja um número. Agora
que você tem um novo componente, entretanto, você terá que instalá-lo (como mencionamos no
capítulo anterior) e substituir os componentes existentes em seus formulários por este novo. Repetir o
mesmo processo para cada um dos componentes que você deseja adaptar consumiria muito
tempo.

Uma alternativa muito mais simples seria usar o idioma da classe interposer (ou seja, definir um
classe com o mesmo nome da classe base, mas em uma unidade diferente). Isto será corretamente
reconhecido pelo compilador e pelo sistema de streaming em tempo de execução, de modo que, em
tempo de execução, você acabará com um objeto da nova classe específica. A única diferença é
que em tempo de design você verá e interagirá com instâncias da classe de componente base.

note As classes Interposer foram mencionadas pela primeira vez e receberam esse nome há muitos anos no The Delphi Maga-
zine. Eles certamente são um pouco complicados, mas às vezes úteis. Considero classes intermediárias, ou seja,
classes com o mesmo nome de uma classe base, mas definidas em uma unidade diferente, mais como uma
linguagem Object Pascal. Observe que para que esse mecanismo funcione é fundamental que a unidade com a
classe interposta esteja listada na instrução uses após aquela com a classe regular que ela deve substituir. Em
outras palavras, os símbolos definidos nas últimas unidades da declaração de usos substituem um símbolo
idêntico definido em unidades anteriormente incluídas. Claro, você sempre pode discriminar os símbolos prefixando-
os com o nome da unidade, mas isso realmente anularia toda a ideia deste hack, que é aproveitar as vantagens
das regras globais de resolução de nomes.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

11: interfaces - 329

Para definir uma classe intermediária, você geralmente escreveria uma nova unidade com uma
classe com o mesmo nome de uma classe base existente. Para se referir à classe base, você
deve prefixá-la com o nome da unidade (ou o compilador irá considerá-la uma definição recursiva):

tipo
TLabel = classe (StdCtrls. TLabel, ITextAndValue)
protegido
procedimento SetText(const Valor: string);
procedimento SetValue (valor const: inteiro);
função GetText: string;
função GetValue: Inteiro;
fim;

Neste caso você não terá que instalar componentes ou mexer nos programas existentes, mas apenas
adicionar uma declaração extra de uso a eles no final da lista. Em ambos os casos (no aplicativo
de demonstração que escrevi que usa classes intermediárias), você pode consultar os componentes
do formulário para esta interface do adaptador e, por exemplo, escrever código para definir todos os
valores como 50, o que afetará diferentes propriedades de diferentes componentes:

era
Intf: ITextAndValue;
Eu: inteiro;
começar
para I := 0 para ComponentCount - 1 faça
se Supports(Components[I], ITextAndValue, Intf) então
Intf.Valor := 50;
fim;

No exemplo específico, este código afetará o valor de uma barra de progresso ou caixa numérica e
o texto de um rótulo ou edição. Ele também ignorará totalmente alguns outros componentes para os
quais não defini a interface do adaptador. Embora este seja apenas um caso muito específico, se você
examinar outros padrões de design, descobrirá facilmente que alguns deles
pode ser melhor implementado aproveitando a flexibilidade extra que as interfaces têm ao longo
classes em Object Pascal (como em Java e C#, apenas para citar outras linguagens populares que
fazem uso extensivo de interfaces).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

330 - 12: manipulação de classes

12: manipulação
Aulas

Nos últimos capítulos você viu os fundamentos do lado do objeto da linguagem Object Pas-cal: classes, objetos,
métodos, construtores, herança, ligação tardia, interfaces e muito mais. Agora precisamos dar um passo
adiante, examinando alguns recursos mais avançados e bastante específicos da linguagem relacionados ao
gerenciamento de classes.
De referências de classe a auxiliares de classe, este capítulo cobre muitos recursos não encontrados em outras
linguagens OOP, ou pelo menos implementados de maneira significativamente diferente.

O foco são as classes, e a manipulação de classes em tempo de execução, um tópico que ampliaremos ainda mais
ao cobrir reflexão e atributos no Capítulo 16.

Métodos de classe e dados de classe


Ao definir uma classe em Object Pascal e na maioria das outras linguagens OOP, você define o
estrutura de dados dos objetos (ou instâncias) da classe e as operações que você pode executar em tal objeto.
Há também a possibilidade, entretanto, de definir dados compartilhados entre todos os objetos da classe e métodos
que podem ser chamados para a classe independentemente de qualquer objeto real criado a partir dela.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 331

Para declarar um método de classe em Object Pascal, basta adicionar a palavra-chave class na frente dele, como
antes da palavra-chave de procedimento ou função :

tipo
TMyClass = turma
função de classe MeanValue: Inteiro;

Dado um objeto MyObject da classe TMyClass, você pode chamar o método aplicando-o a um objeto ou à classe
como um todo:

era
MeuObjeto: TMyClass;
começar
...
I := TMyClass.MeanValue;
J := MeuObjeto.ValorMédio;

Esta sintaxe implica que você pode chamar o método da classe mesmo sem ter uma instância da classe. Existem
cenários de classes feitas apenas de métodos de classe, com a ideia implícita de que você nunca criará objetos
dessas classes (algo que você pode impor declarando o construtor Create como privado).

note O uso de métodos de classe em geral e de classes compostas apenas de métodos de classe em particular é mais
comum em linguagens OOP que não permitem o uso de funções globais. Object Pascal ainda permite declarar
funções globais antiquadas , mas, nos últimos anos, as bibliotecas do sistema e o código escrito pelos
desenvolvedores têm se movido cada vez mais em direção a um uso consistente de métodos de classe.
A vantagem de usar métodos de classe é que eles ficam logicamente vinculados a uma classe, que atua como
uma espécie de espaço de nomes para um grupo de funções relacionadas.

Dados de classe

Os dados da classe são dados compartilhados entre todos os objetos da classe, oferecendo armazenamento
global, mas acesso específico da classe (incluindo limitações de acesso). Como você declara dados de classe?
Simplesmente definindo uma seção da classe marcada com a combinação de palavras-chave class var :

tipo
TMyData = classe
privado
a aula era
FCommonCount: Inteiro;
público
função de classe GetCommon: Inteiro;

A seção class var introduz um bloco de uma ou mais declarações de campos de dados de classe. Você pode
usar uma seção var para declarar outros campos de instância regulares na mesma seção (privado abaixo):

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

332 - 12: manipulação de classes

tipo
TMyData = classe
privado
a aula era
FCommonCount: Inteiro;
era
MaisObjectData: string;
público
função de classe GetCommon: Inteiro;

Além de declarar dados de classe, você também pode definir propriedades de classe, como veremos em uma
seção posterior.

Métodos de aula virtual e o eu oculto


Parâmetro
Embora o conceito de métodos de classe seja compartilhado entre linguagens de programação, a
implementação do Object Pascal tem algumas peculiaridades. Os métodos de primeira classe têm um
parâmetro Self implícito (ou oculto) , bem como os métodos de instância. No entanto, esse parâmetro
oculto Self é uma referência à própria classe, não a uma instância da classe.

À primeira vista, o fato de um método de classe possuir um parâmetro oculto que se refere à própria
classe pode parecer bastante inútil. Afinal, o compilador conhece a classe de um método. Porém, há uma
característica peculiar da linguagem que explica isso: diferentemente da maioria das outras linguagens, na
classe Object Pascal os métodos podem ser virtuais. Em uma classe derivada, você pode substituir um
método de classe de tipo base, como faria com um método regular.

note O suporte para método de classe virtual está conectado com o suporte para construtores virtuais (que são
uma espécie de método de classe de propósito especial). Ambos os recursos não são encontrados em muitas linguagens OOP
compiladas e fortemente tipadas.

Métodos estáticos de classe


Métodos estáticos de classe foram introduzidos na linguagem para compatibilidade de plataforma.
As diferenças entre os métodos de classe comuns e os métodos estáticos de classe são que os métodos
estáticos de classe não têm referências à sua própria classe (nenhum parâmetro Self indicando a própria
classe) e não podem ser virtuais.

Aqui está um exemplo simples com algumas declarações incorretas comentadas, retiradas do exemplo
ClassStatic:

tipo
TBase = classe
privado

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 333

FTmp: Inteiro;
público
procedimento de classe Um;
procedimento de classe Dois; estático;
fim;
procedimento de classe TBase.One;
começar
// Erro: membro da instância 'FTmp' inacessível aqui
// Mostrar(FTmp);
'Um'
Mostrar();
Show(Self.ClassName);
fim;

procedimento de classe TBase.Two;


começar
Mostrar( 'Dois' );
// Erro: Identificador não declarado: 'Self'
// Mostrar(Self.ClassName);
Show(NomeDaClasse);
Dois;
fim;

Em ambos os casos você pode chamar esses métodos de classe diretamente ou invocá-los através de
um objeto:

TBase.Um;
TBase.Dois;

Base := TBase.Create;
Base.Um;
Base.Dois;

Existem dois recursos interessantes que tornam os métodos estáticos de classe úteis no Object Pas-cal. A
primeira é que eles podem ser usados para definir propriedades de classe, conforme descrito na
próxima seção. A segunda é que os métodos estáticos de classe são totalmente compatíveis com a linguagem
C, conforme explicado abaixo.

Métodos de classe estática e retornos de chamada de API do Windows

O fato de não terem nenhum parâmetro Self oculto implica que métodos de classe estáticos podem ser
passados para o sistema operacional (por exemplo, no Windows) como funções de retorno de chamada. Na
prática, você pode declarar um método de classe estático com a convenção de chamada stdcall e usá-lo como
um retorno de chamada direto da API do Windows, como fiz para o método TimerCallBack do exemplo
StaticCallBack :

tipo
TFormCallBack = classe(TForm)
ListBox1: TListBox;
procedimento FormCreate(Remetente: TObject);
privado
a aula era
NTimerCount: Inteiro;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

334 - 12: manipulação de classes

público
procedimento AddToList (const AMessage: string);
procedimento de classe TimerCallBack(hwnd: THandle;
uMsg, idEvent, dwTime: Cardeal); estático; chamada padrão;
fim;
Os dados da classe são usados pelo retorno de chamada como contador. O manipulador OnCreate chama a API
Set-Timer passando o endereço do procedimento de classe estática:

procedimento TFormCallBack.FormCreate(Sender: TObject);


era
Retorno de chamada: TFNTimerProc;
começar
NTimerCount := 0;
Retorno de chamada := TFNTimerProc(@TFormCallBack.TimerCallBack);
SetTimer(Handle, TIMERID, 1000, Retorno de chamada);
fim;

note O parâmetro para TFNTimeProc é um ponteiro de método e é por isso que o nome do método de classe
estática é precedido por um @ ou usando a função Addr . Isso porque precisamos obter o endereço do método,
não execute o método.

Agora a função de retorno de chamada real aumenta o cronômetro e atualiza o formulário, referindo-se a

usando a variável global correspondente – o que deve ser evitado, mas exigiria a adição de alguma
complexidade significativa à demonstração – já que um método de classe não pode se referir ao formulário
como Self:

procedimento de classe TFormCallBack.TimerCallBack(


hwnd: THandle; uMsg, idEvent, dwTime: Cardeal);
começar
tentar
Inc(NTimerCount);
FormCallBack.AddToList(
' no '
IntToStr(NTimerCount) + + TimeToStr(Agora));
exceto em E: Exceção do
Application.HandleException(nil);
fim;
fim;
O bloco try-except existe para evitar que qualquer exceção seja enviada de volta ao Windows – uma regra que
você deve seguir consistentemente para retornos de chamada ou funções DLL.

Propriedades de classe
Uma das razões para usar métodos de classe estáticos é implementar propriedades de classe.
O que é uma propriedade de classe? Como uma propriedade padrão, é um símbolo anexado aos mecanismos de
leitura e gravação. Ao contrário de uma propriedade padrão, ela se relaciona com a classe e deve ser implementada

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 335

mente usando dados de classe ou métodos de classe estáticos. A classe TBase (novamente, do
exemplo ClassStatic ) possui duas propriedades de classe definidas de duas maneiras possíveis:

tipo
TBase = classe
privada
classe var
FMyNome: string; função
de classe
pública GetMyName: string; estático; procedimento de classe
SetMyName(Valor: string); estático; propriedade de classe MeuNome: string
lida GetMyName escreve SetMyName; propriedade de classe DirectName: string lida FMyName
escreve FMyName; fim;

Uma classe com um contador de instâncias


Dados de classe e métodos de classe podem ser usados para armazenar informações sobre
uma classe como um todo. Um exemplo deste tipo de informação poderia ser o número de
instâncias já criadas dessa classe ou o número de instâncias existentes atualmente. O exemplo
CountObj mostra esse cenário. O programa não é muito útil, pois se concentra apenas em um
problema específico. O objeto alvo possui uma classe simples, apenas armazenando um valor numérico:

tipo
TCountedObj = classe(TObject) privado

FValor: Inteiro; classe


privada var

FTotal: Inteiro;
FCatual: Inteiro; construtor
público
Criar; destruidor Destruir;
sobrepor; valor da propriedade: leitura inteira
FValue gravação FValue; função de classe pública GetTotal: Inteiro; função de
classe
GetCurrent: Inteiro; fim;

Cada vez que um objeto é criado, o programa incrementa ambos os contadores. Cada vez que um
objeto é destruído, o contador atual diminui:

construtor TCountedObj.Create(AOwner: TComponent); começar herdado Criar;

Inc(FTtotal); Inc(FCorrente);
fim;

destruidor TCountedObj.Destroy;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

336 - 12: manipulação de classes

começar
Dez(FCorrente);
Destruição herdada;
fim;

As informações da classe podem ser acessadas sem a necessidade de referência a um objeto específico. Na
verdade, pode muito bem acontecer que num determinado momento não existam objetos na memória:

função de classe TCountedObj.GetTotal: Integer;


começar
Resultado := FTotal;
fim;

Você pode exibir o status atual com um código como:

Label1.Text := TCountedObj.GetCurrent.ToString + '/' +


TCountedObj.GetTotal.ToString;

Na demonstração, isso é executado em um timer, que atualiza um rótulo, portanto não precisa se referir a nenhuma
instância específica do objeto nem é acionado diretamente por nenhuma operação manual. Os botões
no exemplo, simplesmente crie e libere alguns dos objetos ou deixe alguns na memória
(na verdade, o programa tem alguns possíveis vazamentos de memória).

Construtores de classe (e destruidores)


Os construtores de classe oferecem uma maneira de inicializar dados relacionados a uma classe e têm a função de
inicializadores de classe, pois na verdade não acabam construindo nada. Um construtor de classe não tem nada a ver
com um construtor de instância padrão: é apenas um código usado para inicializar a própria classe antes que ela seja
usada. Por exemplo, um construtor de classe pode definir valores iniciais para dados de classe, carregar arquivos de
configuração ou de suporte e assim por diante.

No Object Pascal, um construtor de classe é uma alternativa ao código de inicialização da unidade. Caso ambos
existam (em uma unidade), será executado primeiro o construtor da classe e depois o código de inicialização da
unidade. Ao contrário, você pode definir um destruidor de classe que será executado após o código de finalização.

Uma diferença significativa, entretanto, é que, embora o código de inicialização da unidade seja invariavelmente
executado se a unidade for compilada no programa, o construtor e o destruidor da classe serão vinculados apenas
se a classe for realmente usada. Isso significa que o uso do construtor de classe é muito mais amigável ao vinculador
do que o uso do código de inicialização.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 337

note Em outras palavras, com construtores e destruidores de classe, se o tipo não estiver vinculado, a inicialização
o código não faz parte do programa e não é executado. No caso tradicional, o oposto é verdadeiro, o código de
inicialização pode até fazer com que o vinculador traga parte do código da classe, mesmo que ele nunca
seja realmente usado em nenhum outro lugar. Em termos práticos, isso foi introduzido junto com a estrutura de
gestos, uma quantidade bastante grande de código que não é compilado no executável se não for usado.

Em termos de codificação, você pode escrever o seguinte (veja o exemplo ClassCtor ):

tipo
TTestClass = classe
público
a aula era
HoraInicial: TDateTime;
EndTime: TDateTime;
público
construtor de classe Criar;
destruidor de classe Destruir;
fim;

A classe possui dois campos de dados de classe, inicializados pelo construtor da classe e modificados por
um destruidor de classe, enquanto as seções de inicialização e finalização da unidade usam estes
campos de dados:

construtor de classe TTestClass.Create;


começar
Hora de Início := Agora;
fim;

destruidor de classe TTestClass.Destroy;


começar
Fim da Hora := Agora;
fim;

inicialização
ShowMessage(TimeToStr(TTestClass.StartTime));

finalização
ShowMessage(TimeToStr(TTestClass.EndTime));

O que acontece é que a sequência de inicialização funciona conforme o esperado, com os dados da
classe já disponíveis conforme você vai mostrando as informações. Ao fechar, em vez disso, o ShowMessage
a chamada é executada antes que o valor seja atribuído pelo destruidor de classe.

Observe que você pode dar qualquer nome ao construtor e ao destruidor da classe, embora Cre-ate e
Destroy sejam padrões muito bons. Entretanto, você não pode definir vários construtores ou destruidores
de classes. Se você tentar, o compilador emitirá um erro semelhante a este:

[Erro DCC] ClassCtorMainForm.pas(34): E2359 Vários construtores de classe na classe TTestClass: Create e Foo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

338 - 12: manipulação de classes

Construtores de classe no RTL


Existem algumas classes RTL que já aproveitam esse recurso da linguagem, como a classe Exception
que define tanto um construtor de classe (com o código abaixo) quanto um destruidor de classe:

construtor de classe Exception.Create;


começar
InitExceptions;
fim;

O procedimento InitExceptions foi chamado anteriormente na seção de inicialização da unidade


System.SysUtils .

Em geral, acho que usar construtores e destruidores de classe é melhor do que usar inicialização e
finalização de unidade. Na maioria dos casos, isso é apenas um açúcar sintático, então talvez você
não queira voltar e alterar o código existente. Entretanto, se você enfrentar o risco de inicializar
estruturas de dados que você nunca usará (porque nenhuma classe desse tipo é criada), mudar para
construtores de classe fornecerá uma vantagem definitiva. Este é claramente o caso com mais
frequência em uma biblioteca geral, da qual você não usa todos os recursos, do que no código do
aplicativo.

note Um caso muito específico de uso de construtores de classes é no caso de classes genéricas. Vou cobrir isso no
capítulo focado em genéricos.

Implementando o padrão Singleton


Existem classes para as quais faz sentido criar uma e apenas uma instância.
O padrão singleton (outro padrão de design muito comum) exige isso e também sugere ter um ponto
de acesso global para este objeto.

O padrão singleton pode ser implementado de várias maneiras, mas uma abordagem clássica é chamar
a função usada para acessar a única instância exatamente como Instance. Em muitos casos, a
implementação também segue a regra de inicialização lenta, de modo que esta instância global não é
criada quando o programa é iniciado, mas apenas na primeira vez que é necessária.

Na implementação abaixo aproveitei dados de classe, métodos de classe, mas também destruidores
de classe para a limpeza final. Aqui está o código relevante:

tipo
TSingleton = classe(TObject)
público
função de classe Instância: TSingleton;
privado
classe var TheInstance: TSingleton;
destruidor de classe Destruir;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 339

fim;

função de classe TSingleton.Instance: TSingleton;


começar
se TheInstance = nulo então
AInstância := TSingleton.Create;
Resultado := TheInstance;
fim;

destruidor de classe TSingleton.Destroy;


começar
FreeAndNil(AInstância);
fim;

Você pode pegar a instância única da classe (independentemente de ela já ter sido criada ou não)
escrevendo:

era
ASingle: TSingleton;
começar
ASingle := TSingleton.Instance;

Além disso, você pode ocultar o construtor da classe regular, declarando-o privado, de forma que será muito
difícil criar um objeto da classe sem seguir o padrão.

Referências de classe
Tendo examinado vários tópicos relacionados a métodos, podemos agora passar para o tópico de referências
de classes e estender ainda mais nosso exemplo de criação dinâmica de componentes.
O primeiro ponto a ter em mente é que uma referência de classe não é uma classe, não é um objeto e não
é uma referência a um objeto; é simplesmente uma referência a um tipo de classe. Em outras palavras,
uma variável de um tipo de dados de referência de classe tem uma classe como valor.

Um tipo de referência de classe determina o tipo de uma variável de referência de classe. Parece confuso?
Algumas linhas de código podem tornar isso mais claro. Suponha que você tenha definido a classe
TMyClass. Agora você pode definir um novo tipo de referência de classe relacionado a essa classe:

tipo
TMyClassRef = classe de TMyClass;

Agora você pode declarar variáveis de ambos os tipos. A primeira variável refere-se a um objeto, a segunda
a uma classe:

era
AClassRef: TMyClassRef;
UmObjeto: TMyClass;
começar
AClassRef := TMyClass;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

340 - 12: manipulação de classes

AnObject := TMyClass.Create;

Você pode estar se perguntando para que servem as referências de classe. Em geral, as referências de classe permitem
manipular um tipo de dados de classe em tempo de execução. Você pode usar uma referência de classe em qualquer
expressão onde o uso de um tipo de dados seja legal. Na verdade, não existem muitas expressões desse tipo, mas os
poucos casos são interessantes. O caso mais simples é a criação de um objeto. Podemos reescrever as duas linhas acima
da seguinte forma:

AClassRef := TMyClass;
AnObject := AClassRef.Create;

Desta vez apliquei o construtor Create à referência de classe em vez de a uma classe real; Usei uma referência de classe
para criar um objeto dessa classe.

note As referências de classe estão relacionadas ao conceito de metaclasse disponível em outras linguagens OOP. Em
Object Pascal, entretanto, uma referência de classe não é em si uma classe, mas apenas um tipo específico que define
uma referência aos dados da classe. Portanto, a analogia com metaclasses (classes que descrevem outras classes) é um pouco
errôneo. Na verdade, TMetaclass também é o tipo de dados usado no C++Builder.

Quando você tem uma referência de classe, você pode acessar qualquer método de classe da classe. Então, se TMyClass
tivesse um método de classe chamado Foo, você seria capaz de escrever:

TMyClass.Foo
AClassRef.Foo

Isso não seria muito útil se as referências de classe não suportassem a mesma regra de compatibilidade de tipo que se aplica
aos tipos de classe. Ao declarar uma variável de referência de classe, como MyClassRef acima, você pode atribuir a ela
aquela classe específica e qualquer subclasse.
Então, se MyNewClass for uma subclasse da minha classe, você também pode escrever

AClassRef := MinhaNovaClasse;

Para entender por que isso pode realmente ser interessante, você deve lembrar que os métodos de classe que você
pode chamar para uma referência de classe podem ser virtuais, portanto, a subclasse específica pode substituí-
los. Usando referências de classe e métodos de classe virtuais, você pode implementar uma forma de polimorfismo
no nível do método de classe que poucas (se houver) das outras linguagens OOP estáticas suportam.
Considere também que cada classe herda de TObject, então você pode aplicar a cada referência de classe
alguns dos métodos de TObject, incluindo Instance-Size, ClassName, ParentClass e InheritsFrom. Discutirei esses
métodos de classe e outros métodos da classe TObject no Capítulo 17.

Referências de classe no RTL


A unidade System e outras unidades principais RTL declaram muitas referências de classe, incluindo o seguinte:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 341

TClass = classe do TObject;


ExceptClass = classe de Exceção;
TComponentClass = classe de TComponent;
TControlClass = classe do TControl;
TFormClass = classe do TForm;

Em particular, o tipo de referência de classe TClass pode ser usado para armazenar uma referência a
qualquer classe que você escreve em Object Pascal, porque cada classe é, em última análise, derivada de TObject.
A referência TFormClass , em vez disso, é usada no código-fonte do projeto Object Pas-cal padrão baseado
em FireMonkey ou VCL. O método CreateForm do objeto Application para ambas as bibliotecas,
de fato, requer como parâmetro a classe do formulário a ser criado:

Application.CreateForm(TMyForm1, MeuFormulário);

O primeiro parâmetro é uma referência de classe, o segundo é uma variável que receberá uma referência à
instância do objeto criado.

Criando componentes usando referências de classe


Qual é o uso prático de referências de classe em Object Pascal? Ser capaz de manipular
um tipo de dados em tempo de execução é um elemento fundamental do ambiente. Ao adicionar um novo
componente a um formulário selecionando-o na Paleta de Componentes, você seleciona um tipo de dados e
cria um objeto desse tipo de dados. (Na verdade, é isso que o ambiente de desenvolvimento faz por
você nos bastidores.)

Para lhe dar uma ideia melhor de como funcionam as referências de classe, criei um exemplo chamado
ClasseRef. O formulário exibido neste exemplo é bastante simples. Possui três botões de rádio
colocados dentro de um painel na parte superior do formulário. Ao selecionar um desses botões de
opção e clicar no formulário, você poderá criar novos componentes dos três tipos indicados pelos rótulos
dos botões: botões de opção, botões normais e caixas de edição. Para que este programa funcione
corretamente, ele precisa alterar os nomes dos três componentes, pois os nomes dos componentes devem
ser exclusivos. O formulário também deve ter um campo de referência de classe:

privado
FControlType: TControlClass;
FControlNo: Inteiro;

O primeiro campo armazena um novo tipo de dados toda vez que o usuário clica em um dos três botões
de opção, alterando seu status. Aqui está um dos três métodos:

procedimento TForm1.RadioButtonRadioChange(Remetente: TObject);


começar
FControlType := TRadioButton;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

342 - 12: manipulação de classes

Os outros dois botões de opção possuem manipuladores de eventos OnChange semelhantes a este,
atribuindo o valor TEdit ou TButton ao campo FControlType . Uma atribuição semelhante também está
presente no manipulador do evento OnCreate do formulário, usado como método de inicialização. A
parte interessante do código é executada quando o usuário clica em um TLayout
controle que cobre a maior parte da superfície do formulário. Escolhi o evento OnMouseDown do formulário
para manter a posição do clique do mouse:

procedimento TForm1.Layout1MouseDown(Sender: TObject;


Botão: TMouseButton; Mudança: TShiftState; X, Y: Único);
era
NovoCtrl: TControl;
NovoNome: string;
começar
// Criar o controle
NewCtrl := FControlType.Create(Self);

// Esconda-o temporariamente, para evite tremer


NewCtrl.Visible := Falso;

// Definir pai e posição


NewCtrl.Parent := Layout1;
NewCtrl.Position.X := X;
NewCtrl.Position.Y := Y;

// o Calcular nome exclusivo (e texto)


Inc(FControlNo);
NovoNome := FControlType.ClassName + FControlNo.ToString;
Excluir(NovoNome, 1, 1);
NovoCtrl.Nome := NovoNome;

// Agora mostre
NewCtrl.Visible := Verdadeiro;
fim;

A primeira linha do código deste método é a chave. Ele cria um novo objeto do tipo de dados de classe
armazenado no campo FControlType . Conseguimos isso simplesmente aplicando o construtor Create à
referência de classe.

Agora você pode definir o valor da propriedade Parent , definir a posição do novo componente, dar-lhe
um nome (que também é usado automaticamente como Texto) e torná-lo visível.

Observe em particular o código usado para construir o nome: para imitar a convenção de nomenclatura
padrão do Object Pascal, peguei o nome da classe com a expressão FControlType-
.ClassName, usando um método de classe da classe TObject . Depois adicionei um número no final do
nome e removi a letra inicial da string.

Para o primeiro botão de opção, digamos que FControlNo seja 1 e então FControlType seja
TRadioButton; portanto, FControlType.ClassName retorna a string 'TRadioButton' e
FControlNo.AsString retorna '1' que anexamos ao final antes de copiarmos a string menos o primeiro
caractere, ou seja, menos o 'T' do início da string, para forneça a string final 'RadioButton1' que usaremos
para a nova instância. Parece familiar?

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 343

Figura 12.1:
Um exemplo da saída
do aplicativo ClassRef em
execução na janela

Você pode ver um exemplo da saída deste programa na Figura 12.1. Observe que a nomenclatura não
é exatamente a mesma usada pelo IDE, que usa um contador separado para cada tipo de controle.

Este programa usa um único contador para todos os componentes. Portanto, se você colocar um botão,
seguido por um botão de opção, depois duas edições e depois outro botão, ao executar o aplicativo
ClassRef, seus nomes serão Button1, RadioButton2, Edit3, Edit4, Button5 como mostra a imagem.

Observe que, diferentemente do designer, os controles de edição criados não têm visibilidade de seus
nomes.

À parte, considere que uma vez criado um componente genérico, você pode acessar suas propriedades de uma forma muito
dinâmica, usando reflexão, um tópico abordado em detalhes no Capítulo 16. Nesse mesmo capítulo veremos que existem
outras maneiras para se referir a informações de tipo e classe ao lado de referências de classe.

Auxiliares de aula e registro


Como vimos no Capítulo 8, o conceito de herança entre classes é uma forma de expandir uma classe
fornecendo novos recursos, sem afetar a implementação original em
de qualquer forma. Esta é uma implementação do chamado princípio aberto-fechado: o tipo de dados é
totalmente definido (fechado), mas ainda modificável (aberto).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

344 - 12: manipulação de classes

Embora a herança de tipo seja um mecanismo extremamente poderoso, há cenários em que ela não é
ideal. O fato é que, ao trabalhar com bibliotecas existentes e complexas, você pode querer aumentar um tipo
de dados sem herdar um novo. Isto é particularmente verdadeiro quando os objetos são criados de forma
automática e a substituição da sua criação pode ser extremamente complexa.

Um cenário bastante óbvio para desenvolvedores de Object Pascal é o uso de componentes. Se você quiser
adicionar um método a uma classe de componente, para fornecer algum novo comportamento a ele, você
pode de fato usar herança, mas isso implica que se crie o novo tipo derivado, crie
um pacote para instalá-lo e substituir todos os componentes existentes em formulários e outras superfícies de
design pelo novo tipo de componente (uma operação que afeta a definição do formulário e o arquivo de
código-fonte).

A abordagem alternativa é usar um auxiliar de classe ou registro. Esses tipos de dados para fins especiais
podem estender um tipo existente com novos métodos. Mesmo que tenham algumas limitações, os auxiliares
de classe permitem lidar com um cenário como o que acabei de descrever simplesmente adicionando
novos métodos a um componente existente, sem a necessidade de modificar o tipo real do componente.

note Na verdade, já vimos uma abordagem alternativa para estender uma classe de biblioteca sem substituir
completamente suas referências, usando o idioma da classe interposer , ou seja, usando herança e com uma
classe de mesmo nome. Abordei esse idioma na seção final do capítulo anterior. Os ajudantes de classe
oferecem um modelo mais limpo; entretanto, eles não podem ser usados para substituir métodos virtuais ou
implementar uma interface extra, como fiz na aplicação do capítulo anterior.

Ajudantes de classe

Um auxiliar de classe é uma forma de adicionar métodos e propriedades a uma classe que você não tem
poder de modificar (como uma classe de biblioteca). Usar um auxiliar de classe para estender uma classe
em seu próprio código é realmente incomum, pois nesse caso você geralmente deve seguir em frente e alterar
a classe real.

Embora a ideia de auxiliares de classe seja específica do Object Pascal do Delphi, outras linguagens de
programação possuem conceitos como métodos de extensão ou classes implícitas que oferecem um recurso
semelhante.

O que você não pode fazer em um auxiliar de classe é adicionar dados de instância, já que os dados devem
residir nos objetos reais e estes são definidos por sua classe original, ou tocar nos métodos virtuais,
novamente definidos na estrutura física da classe original. Em outras palavras, uma classe auxiliar só pode
adicionar ou substituir métodos não virtuais de uma classe existente. Desta forma você poderá aplicar o
novo método a um objeto da classe original, mesmo que isso
classe não tem ideia da existência do método.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 345

Se isso não estiver claro, e provavelmente não está, vejamos um exemplo retirado do
Exemplo ClassHelperDemo – uma demonstração do que você não deve fazer, ou seja, usar auxiliares de classe
para aumentar sua própria classe:

tipo
TMyObject = classe
protegido
Valor: Inteiro;
Texto: sequência;
público
procedimento Aumento;
fim;

TMyObjectHelper = auxiliar de classe para TMyObject


público
procedimento Mostrar;
fim;

O código anterior declara uma classe e um auxiliar para esta classe. Isso significa que para um objeto do tipo
TMyObject, você pode chamar os métodos da classe original, bem como os métodos da classe auxiliar:

Obj := TMyObject.Create;
Obj.Texto := ; 'Foo'
Obj.Mostrar;

Os métodos da classe auxiliar tornam-se parte da classe e podem usar Self como qualquer outro
método para se referir ao objeto atual (da classe ajuda porque os auxiliares de classe não são instanciados),
como este código demonstra:

procedimento TMyObjectHelper.Show;
começar
' ' ' -- '
Show(Texto + + IntToStr(Valor) + +
' -- '
NomeDaClasse + ToString);
+ fim;

Finalmente, observe que um método de classe auxiliar pode substituir o método original. No código, adicionei um
método Show à classe e à classe auxiliar, mas apenas o método da classe auxiliar é chamado!

É claro que faz muito pouco sentido declarar uma classe e uma extensão para a mesma classe usando a
sintaxe auxiliar de classe na mesma unidade ou mesmo no mesmo programa. Fiz isso na demonstração apenas
para facilitar a compreensão dos detalhes técnicos dessa sintaxe.

Os auxiliares de classe não devem ser usados como uma construção de linguagem geral para o desenvolvimento
de arquiteturas de aplicativos, mas destinam-se principalmente a estender classes de biblioteca para as quais
você não possui o código-fonte ou não deseja alterá-lo para evitar conflitos futuros.

Existem mais algumas regras que se aplicam aos auxiliares de classe, ou seja, métodos auxiliares de classe:

· pode ter especificadores de acesso diferentes do método original na classe

· podem ser métodos de classe ou métodos de instância, variáveis de classe e propriedades

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

346 - 12: manipulação de classes

· podem ser métodos virtuais, que podem ser substituídos em uma classe derivada (embora eu ache isso um pouco
estranho em termos práticos)
· pode introduzir construtores extras

· pode adicionar constantes aninhadas à definição de tipo

O único recurso que falta por design são os dados de instância. Observe também que os auxiliares de classe são
habilitados à medida que se tornam visíveis no escopo. Você precisa adicionar uma instrução de uso referente à
unidade que declara o auxiliar de classe para ver seus métodos, e não apenas incluí-lo uma vez
no processo de compilação.

note Por algum tempo, ocorreu um erro no compilador Delphi que acabou permitindo que os auxiliares de classe
acessar campos privados da classe que ajudaram, independente da unidade em que a classe foi declarada.
Esse “hack” basicamente quebrou as regras de encapsulamento da programação orientada a objetos. Para impor a
semântica de visibilidade, os auxiliares de classe e registro nas versões mais recentes dos compiladores Object Pascal
(começando com Delphi 10.1 Berlin) não podem acessar membros privados das classes ou registros que eles estendem.
Na verdade, isso fez com que o código existente não funcionasse mais, código que aproveitava esse hack (que
nunca foi planejado para ser um recurso de linguagem).

Um auxiliar de classe para uma caixa de listagem

Um uso prático dos auxiliares de classe é fornecer métodos extras para classes de biblioteca. A razão é que
você não deseja alterar essas classes diretamente (mesmo se você tiver o código-fonte, você realmente não
deseja editar as fontes da biblioteca principal) ou herdar delas (pois isso forçaria você a substituir os componentes
no formulários em tempo de design).

Como exemplo, considere este caso simples: você deseja uma maneira simples de obter o texto da seleção atual
de uma caixa de listagem. Em vez de escrever o código clássico:

ListBox1.Items[ListBox1.ItemIndex]

você pode definir um auxiliar de classe da seguinte maneira (retirado do projeto ControlHelper ):

tipo
TListBoxHelper = auxiliar de classe para TListBox
função ItemIndexValue: string;
fim;

função TListBoxHelper.ItemIndexValue: string;


começar
Resultado:= '';
se ItemIndex >= 0 então
Resultado:= Itens[ItemIndex];
fim;

Agora você pode se referir ao item selecionado da caixa de listagem como:

Show(ListBox1.ItemIndexValue);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 347

Este é apenas um caso muito simples, mas que mostra a ideia em termos muito práticos.

Auxiliares de classe e herança


A limitação mais significativa dos ajudantes é que você pode ter apenas um ajudante para cada classe por
vez. Se o compilador encontrar duas classes auxiliares, a segunda substituirá a primeira. Não há como
encadear auxiliares de classe, ou seja, ter um auxiliar de classe que estenda ainda mais uma classe já
estendida com outro auxiliar de classe.

Uma solução parcial para esse problema vem do fato de que você pode introduzir um auxiliar de classe
para uma classe e adicione mais um auxiliar de classe para uma classe herdada; mas você não pode
herdar diretamente um auxiliar de classe de outro auxiliar de classe. Eu realmente não incentivo entrar em
estruturas auxiliares de classe complexas, porque elas podem realmente transformar seu código em uma
bagunça muito complicada.

Os auxiliares realmente fazem sentido quando você adiciona itens a uma biblioteca ou estrutura de dados
definida pelo sistema. Um exemplo seria o registro TGUID , uma estrutura de dados do Windows que você
pode usar em várias plataformas no Object Pascal, que possui um auxiliar que adiciona alguns recursos
comuns:

tipo
TGUIDHelper = auxiliar de registro para TGUID
função de classe Criar (const B: TBytes): TGUID; sobrecarga; estático;
função de classe Criar (const S: string): TGUID; sobrecarga; estático;
// ... Várias sobrecargas de criação omitidas
função de classe NewGuid: TGUID; estático;
função ToByteArray: TBytes;
função ToString: string;
fim;

Você deve ter notado que TGUIDHelper é um auxiliar de registro em vez de um auxiliar de classe.
Sim, os registros podem ter ajudantes assim como as classes.

Adicionando enumeração de controle com uma classe


Ajudante
Qualquer componente Delphi nas bibliotecas define automaticamente um enumerador que você pode usar
para processar cada um dos componentes próprios ou componentes filhos. Por exemplo, em um método de
formulário, você pode enumerar os componentes pertencentes ao formulário escrevendo:

para var AComp em Self do


... // Usar AComp

Outra operação comum é navegar pelos controles filhos, que incluem apenas componentes visuais (excluindo
componentes não visuais como um TMainMenu) que possuem o formato como

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

348 - 12: manipulação de classes

pai direto (excluindo controles hospedados por um controle filho, como um botão em um painel). Uma
técnica que podemos usar para escrever um código mais simples para alternar entre controles filhos é
adicionar uma nova enumeração à classe TWinControl escrevendo o seguinte auxiliar de classe:

tipo
TControlsEnumHelper = auxiliar de classe para o tipo TWinControl

TControlsEnum = classe privada

NPos: Inteiro;
FControl: TWinControl; construtor
público
Criar (AControl: TWinControl); função MoveNext: Booleano; função
GetCurrent: TControl; propriedade Atual:
TControl lê GetCurrent; fim; função pública
GetEnumerator: TControlsEnum; fim;

note A razão pela qual o auxiliar é para TWinControl e não para TControl é que apenas controles com um identificador
de janela podem ser pais de outros controles. Isso basicamente exclui controles gráficos.

Este é o código completo do helper, incluindo seu único método e aqueles da classe aninhada
TControlsEnum:

{ TControlsEnumHelper }

função TControlsEnumHelper.GetEnumerator: TControlsEnum; resultado inicial: =

TControlsEnum.Create (Self); fim;

{ TControlsEnumHelper.TControlsEnum }

construtor TControlsEnumHelper.TControlsEnum.Create(AControl: TWinControl);


começar FControl := AControl;
NPos:=
-1; fim;

função TControlsEnumHelper.TControlsEnum.GetCurrent: TControl; começar Resultado :=

FControl.Controls[NPos]; fim;

função TControlsEnumHelper.TControlsEnum.MoveNext: Boolean; começar Inc(NPos);

Resultado := NPos
< FControl.ControlCount; fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 349

Agora, se criarmos um formulário como o da Figura 12.2, poderemos testar a enumeração em vários
cenários. O primeiro caso é para o qual escrevemos especificamente o código, ou seja, enumerar os
controles filhos do formulário:

Figura 12.2: O
formulário usado para
testar o auxiliar de
enumeração de controle
em tempo de design
no Delphi IDE

procedimento TControlEnumForm.BtnFormChildClick(Sender: TObject);


começar
Memo1.Lines.Add (para 'Forma Criança' );
var ACtrl em Self do
Memo1.Lines.Add(ACtrl.Nome);
''
Memo1.Lines.Add(end; );

Esta é a saída da operação no controle memo, listando os controles filho do formulário, mas não outros
componentes ou controles criados pelo painel:

Formulário Filho
Memo1
BtnFormChild
Editar1
Caixa de seleção1
RadioButton1
Painel1
BtnFormComps
BtnButtonChild

A lista completa aparecerá se enumerarmos todos os componentes. No entanto, temos um problema ao


usar o código que mostrei no início desta seção, porque substituímos o método GetNumerator por uma
nova versão (no auxiliar de classe) e por esse motivo não podemos acessar diretamente o enumerador
base do TComponent . O auxiliar está definido para TWinControl, então podemos usar um truque.
Se convertermos nosso objeto em TComponent , o código invocará o enumerador padrão predefinido:

procedimento TControlEnumForm.BtnFormCompsClick(Sender: TObject);


começar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

350 - 12: manipulação de classes

Memo1.Lines.Add(para 'Componentes do formulário');


var AComp em TComponent(Self) faça
Memo1.Lines.Add(AComp.Nome);
Memo1.Lines.Add('');
fim;

Esta é a saída, listando mais componentes do que a lista anterior:

Componentes do formulário
Memo1
BtnFormChild
Editar1
Caixa de seleção1
RadioButton1
Painel1
BtnPanelChild
Caixa de combinação1

BtnFormComps
BtnButtonChild
Menu Principal1

No exemplo ControlsEnum também adicionei código para enumerar os controles filhos do painel e os de um dos
botões (principalmente para testar se o enumerador funciona corretamente quando a lista está vazia).

Auxiliares de registro para tipos intrínsecos


Uma extensão adicional dos conceitos auxiliares de registro é a capacidade de adicionar métodos aos arquivos nativos.
(ou tipos de dados intrínsecos do compilador) . Embora a mesma sintaxe de “auxiliar de registro” seja usada, ela
não se aplica a registros, mas a tipos de dados regulares.

note Os auxiliares de registro são usados atualmente para aumentar e adicionar operações semelhantes a métodos a tipos de
dados nativos, mas isso também pode mudar no futuro. A biblioteca de tempo de execução de hoje define alguns auxiliares
nativos que podem desaparecer no futuro, preservando a maneira como você escreve o código que usa esses auxiliares...
mas quebrando a compatibilidade no código que os define. É por isso que você não deve abusar desse recurso, mesmo
que seja certamente muito agradável e prático.

Como os auxiliares de tipo intrínseco funcionam na prática? Vamos considerar a seguinte definição de auxiliar para o
tipo Integer :

tipo
TIntHelper = auxiliar de registro para Integer
função AsString: string;
fim;

Agora, dada uma variável inteira N, você pode escrever:

N.AsString;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 351

Como você define esse pseudométodo e como ele pode se referir ao valor da variável? Ampliando o
significado do identificador próprio para se referir ao valor ao qual a função é aplicada:

função TIntHelper.AsString: string;


começar
Resultado:= IntToStr(Self);
fim;

Observe que você também pode aplicar métodos a constantes, como em:

Legenda:= 400000.AsString;

No entanto, você não pode fazer o mesmo para um valor pequeno, pois o compilador interpreta as
constantes como sendo do menor tipo possível. Portanto, se você deseja obter o valor da string 4,
que corresponde a um byte, você deve usar a segunda forma:

Legenda:= 4.AsString; Legenda:= // Não!


Integer(4).AsString; // OK

Ou você pode fazer a primeira instrução a ser compilada definindo um auxiliar diferente:

tipo
TByteHelper = auxiliar de registro para Byte...

Como já vimos no Capítulo 2, você realmente não precisa escrever o código acima para tipos como
Integer e Byte, pois a biblioteca de tempo de execução define uma lista bastante abrangente de
auxiliares de classe para a maioria dos tipos de dados principais, incluindo os seguintes, que são definidos
na unidade System.SysUtils :

TGUIDHelper = auxiliar de registro para TGUID


TStringHelper = auxiliar de registro para string
TSingleHelper = auxiliar de registro para Single
TDoubleHelper = auxiliar de registro para Double
TExtendedHelper = auxiliar de registro para Extended
TByteHelper = auxiliar de registro para Byte
TShortIntHelper = auxiliar de registro para ShortInt
TWordHelper = auxiliar de registro para Word
TSmallIntHelper = auxiliar de registro para SmallInt
TCardinalHelper = auxiliar de registro para Cardinal
TIntegerHelper = auxiliar de registro para Integer
TUInt64Helper = auxiliar de registro para UInt64
TInt64Helper = auxiliar de registro para Int64
TNativeUIntHelper = auxiliar de registro para NativeUInt
TNativeIntHelper = auxiliar de registro para NativeInt
TBooleanHelper = auxiliar de registro para Boolean
TByteBoolHelper = auxiliar de registro para ByteBool
TWordBoolHelper = auxiliar de registro para WordBool
TWordBoolHelper = auxiliar de registro para WordBool
TLongBoolHelper = auxiliar de registro para LongBool
TCurrencyHelper = auxiliar de registro para moeda // adicionado em Delfos 11

Existem alguns outros auxiliares de tipo intrínseco atualmente definidos em outras unidades, como:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

352 - 12: manipulação de classes

//Sistema.Character:
TCharHelper = auxiliar de registro para Char
//System.Classes:
TUInt32Helper = auxiliar de registro para UInt32
//System.DateUtils
TDateTimeHelper = auxiliar de registro para TDateTime // adicionado em Delfos 11

Dado que abordei o uso desses auxiliares em muitos exemplos na parte inicial do livro, não há necessidade de
reiterá-los aqui. O que esta seção adicionou é uma descrição de como você pode definir um auxiliar de tipo
intrínseco.

Auxiliares para aliases de tipo


Como vimos, não é possível definir dois helpers para o mesmo tipo, muito menos um intrínseco
tipo. Então, como você adiciona uma operação direta extra a um tipo nativo, como Integer? Embora não exista
uma solução clara, existem algumas soluções alternativas possíveis (além de copiar o código-fonte do auxiliar
de classe interno e duplicá-lo com o método extra).

Uma solução que gosto é a definição de um alias de tipo. Um alias de tipo é visto como um tipo totalmente novo
pelo compilador, portanto ele pode ter seu próprio auxiliar sem substituir o auxiliar do tipo original. Agora, como
os tipos são separados, você ainda não pode aplicar métodos de ambas as classes
ajudantes para a mesma variável, mas um dos conjuntos será um tipo rejeitado. Deixe-me explicar
isso em termos de código. Suponha que você crie um alias de tipo como:

tipo
MeuInt = tipo Inteiro;

Agora você pode definir um auxiliar para este tipo:

tipo
TMyIntHelper = auxiliar de registro para MyInt
função AsString: string;
fim;

função MyIntHelper.AsString: string;


começar
Resultado:= IntToStr(Self);
fim;

Se você declarar uma variável deste novo tipo, o acesso padrão permitirá que você invoque o auxiliar do novo tipo,
mas você ainda poderá acessar o auxiliar do tipo Integer com uma conversão - este código está no exemplo
TypeAliasHelper , para você experimentar mais variações:

procedimento TForm1.Button1Click(Remetente: TObject);


era
O QUE: MeuInt;
começar
MI:= 10;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

12: manipulação de classes – 353

Mostrar(MI.AsString);
// Mostrar(MI.ToString); //Isso não funciona
Mostrar(Integer(MI).ToString);
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

354 - 13: objetos e memória

13: objetos e
memória

Este capítulo focou em um tópico muito específico e bastante importante, que é o gerenciamento de
memória na linguagem Object Pascal. A linguagem e seu ambiente de execução oferecem uma
solução única que fica entre o gerenciamento manual de memória no estilo C++ e a coleta
automática de lixo no estilo Java ou C#.

A razão para esta abordagem intermediária é que ela ajuda a evitar a maioria dos problemas de
gerenciamento manual de memória (mas claramente não todos), sem as restrições e os problemas
causados pela coleta automática de lixo, desde a alocação extra de memória até o descarte não
determinístico.

note Não tenho nenhuma intenção particular de me aprofundar nos problemas das estratégias de GC (Coleta de Lixo) e como
elas são implementadas nas diversas plataformas. Este é mais um tema de pesquisa. O que é relevante
é que em dispositivos restritos, como os móveis, o GC está longe de ser ideal, mas alguns dos mesmos
problemas se aplicam a todas as plataformas. A tendência de ignorar o consumo de memória dos aplicativos do
Windows nos trouxe pequenos utilitários que consomem grandes quantidades de memória cada.

O que torna as coisas um pouco mais complicadas no Object Pascal, porém, é o fato de que uma
variável usa memória dependendo de seu tipo de dados, com alguns tipos usando contagem de
referência e alguns uma abordagem mais tradicional, incluindo o modelo de propriedade baseado em
componentes da VCL, e um poucas outras opções, tornam o gerenciamento de memória uma tarefa complexa

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 355

tema. Este capítulo está aqui para abordar isso, começando com alguns dos fundamentos do gerenciamento
de memória em linguagens de programação modernas e os conceitos por trás do modelo de referência de
objeto.

note Por muitos anos, os compiladores móveis Delphi ofereceram um modelo de memória diferente chamado ARC, ou Auto-
Contagem de referência automática. Promovido pela Apple em linguagens próprias, o ARC adiciona suporte do
compilador para rastrear e contar referências a um objeto, destruindo-o quando ele não for mais necessário, ou seja,
a contagem de referências chega a zero). Isto é muito semelhante ao que acontece com referências de interface no
Delphi em todas as plataformas. A partir do Delphi 10.4 o suporte ao ARC foi removido da linguagem para todas
as plataformas.

Dados globais, pilha e heap


A memória usada por qualquer aplicação Object Pascal em qualquer plataforma pode ser dividida em
duas áreas: código e dados. Em termos de código, partes do arquivo executável de um programa,
seus recursos (como bitmaps e descrições de formulários) e as bibliotecas utilizadas pelo programa são
carregados em seu espaço de memória. Esses blocos de memória são somente leitura e (em algumas
plataformas como o Windows) podem ser compartilhados entre vários processos.

É mais interessante olhar para a parte dos dados. Os dados de um programa Object Pascal (como os de
programas escritos na maioria das outras linguagens) são armazenados em três áreas claramente
distintas: a memória global, a pilha e o heap.

A Memória Global
Quando o compilador Object Pascal gera o arquivo executável, ele determina o espaço necessário para
armazenar variáveis que existem durante todo o tempo de vida do programa. Variáveis globais declaradas na
interface, ou nas porções de implementação de uma unidade, enquadram-se neste
categoria. Observe que, se uma variável global for de um tipo de classe (mas também uma string ou uma
matriz dinâmica), apenas uma referência de objeto de 4 ou 8 bytes será armazenada na memória global.

Você pode determinar o tamanho da memória global usando o projeto | Item de menu de informações após
compilar um programa. O campo específico que você deseja examinar é o tamanho dos dados. A Figura
13.1 mostra o uso de quase 50K de dados globais (48.956 bytes), que inclui dados globais do seu
programa e das bibliotecas que ele usa.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

356 - 13: objetos e memória

Figura 13.1: As
informações sobre um
programa compilado

A memória global às vezes é chamada de memória estática, pois depois que o programa é carregado,
as variáveis permanecem em seu local original, pois a memória nunca é liberada durante a vida útil
do programa.

A pilha

A pilha é uma área de memória dinâmica, que é alocada e desalocada seguindo a ordem LIFO, ou
seja, Último a Entrar, Primeiro a Sair. Isso significa que o último objeto de memória alocado será o
primeiro a ser excluído. Você pode ver uma representação da pilha de memória na Figura 13.2.

A memória de pilha normalmente é usada por chamadas de procedimentos, funções e métodos para
passar parâmetros e seus valores de retorno e para as variáveis locais que você declara em uma
função ou método. Depois que uma chamada de rotina é encerrada, sua área de memória na pilha é
liberada. Lembre-se que Object Pascal por padrão usa convenção de chamada de registro, que
ou seja, os parâmetros são passados nos registros da CPU em vez da pilha sempre que possível.

Figura 13.2: Uma


representação da área
de memória da pilha

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 357

Observe também que, para economizar tempo, a memória da pilha geralmente não é inicializada nem limpa.
É por isso que se você declarar, digamos, um número inteiro como uma variável local e apenas ler seu valor,
não saberá qual será seu valor. É por isso que todas as variáveis locais precisam ser inicializadas antes
de serem usadas.

O tamanho da pilha geralmente é fixo e determinado pelo processo de compilação. Você pode definir esse
parâmetro na página do vinculador do Projeto | Opções. No entanto, o padrão geralmente é OK. Se você
receber uma mensagem de erro de “estouro de pilha”, provavelmente é porque você tem uma
função que se autodenomina recursivamente para sempre, e não porque o espaço da pilha é muito limitado.
O tamanho inicial da pilha é outra informação fornecida pelo Projeto | Diálogo de informações.

A pilha
O heap é a área na qual a alocação e desalocação de memória acontecem em ordem aleatória. Isso
significa que, se você alocar três blocos de memória em sequência, eles poderão ser destruídos
posteriormente em qualquer ordem. O gerenciador de heap cuida de todos os detalhes, então você
simplesmente solicita uma nova memória com a função GetMem de baixo nível ou chamando um
construtor para criar um objeto, e o sistema retornará um novo bloco de memória para você
(possivelmente reutilizando blocos de memória já descartado). Object Pascal usa o heap para alocar a
memória de cada objeto, o texto das strings, para matrizes dinâmicas e para a maioria das outras
estruturas de dados.

Por ser dinâmico, o heap é a área da memória onde os programas geralmente apresentam mais problemas:

· Cada vez que um objeto é criado, ele precisa ser destruído. Deixar de fazer isso é um cenário
chamado “vazamento de memória”, que não causará muitos danos, a menos que seja repetido
indefinidamente até que a memória heap seja consumida.

· Cada vez que um objeto é destruído, você deve garantir que ele não seja mais usado e que o programa
não tente destruí-lo uma segunda vez.

· O mesmo se aplica a qualquer outra estrutura de dados criada dinamicamente, mas a linguagem
O tempo de execução cuida de strings e arrays dinâmicos de maneira quase automática, então você
quase nunca precisa se preocupar com isso.

O modelo de referência de objeto


Como vimos no Capítulo 7, os objetos da linguagem são implementados como referências. A
variável de um tipo de classe é apenas um ponteiro para o local da memória no heap onde residem os
dados do objeto. Na verdade, há um pouco de informação extra, como uma referência de classe, uma maneira

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

358 - 13: objetos e memória

para acessar a tabela de métodos virtuais do objeto, mas isso está fora do escopo deste capítulo (apresentarei
isso em breve na seção “Este ponteiro é uma referência de objeto?” do Capítulo 13).

Também vimos como atribuir um objeto a outro apenas faz uma cópia da referência, então você terá duas
referências para um único objeto na memória. Para ter dois objetos completamente separados,
você precisa criar um segundo e copiar os dados do primeiro objeto para ele (uma operação não disponível
automaticamente, pois seus detalhes de implementação podem variar dependendo da estrutura de dados real).

Em termos de codificação, se você escrever o código a seguir, não criará um segundo objeto, mas sim uma
nova referência a um objeto existente:

era
Botão2: Botão T;
começar
Botão2 := Botão1;

Em outras palavras, há apenas um objeto na memória, e tanto o Button1 quanto o Button2


variáveis referem-se a ele, como você pode ver na Figura 13.3.

Figura 13.3: Copiando


referências de objetos

Passando objetos como parâmetros


Algo semelhante acontece quando você passa um objeto como parâmetro para uma função ou método.
Em termos gerais, você está apenas copiando a referência para o mesmo objeto e, então, dentro do
método ou função, você acessa esse objeto e executa operações nesse objeto e modifica seus dados
independentemente do fato de o parâmetro ser passado como um parâmetro const ou não.

Por exemplo, escrevendo este procedimento e chamando-o da seguinte forma, você modificará a legenda
de Button1, ou AButton se preferir:

procedimento ChangeCaption(AButton: TButton; Texto: string);


começar
AButton.Text := Texto;
fim;

// Chamar...
ChangeCaption(Button1, 'Olá')

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 359

E se você precisar criar um novo objeto? Basicamente, você terá que criá-lo e depois copiar cada uma das
propriedades relevantes. Algumas classes, notadamente a maioria das classes derivadas de TPersistent, e não de
TComponent, definem um método Assign para copiar os dados
de um objeto. Por exemplo, você pode escrever:

ListBox1.Items.Assign(Memo1.Lines);

Mesmo se você atribuir essas propriedades diretamente, o Object Pascal executará código semelhante para você.
Na verdade, o método SetItems conectado à propriedade items da caixa de listagem chama o método Assign
da classe TStringList que representa os itens reais da caixa de listagem.

Então, vamos tentar recapitular o que os vários modificadores de passagem de parâmetros fazem quando aplicados
a objetos:

· Se não houver modificador, você pode fazer qualquer operação no objeto e na variável referente a ele. Você
pode modificar o objeto original, mas se atribuir um novo objeto ao parâmetro, esse novo objeto não terá nada
a ver com o original e com a variável referente a ele.

· Se houver um modificador const , você poderá alterar valores e chamar métodos do objeto, mas não poderá
atribuir um novo objeto ao parâmetro. Observe que não há vantagem de desempenho em passar um objeto
como const.

· Se houver um modificador var , você pode alterar qualquer coisa no objeto e também substituir o objeto original
por um novo no local de chamada, como acontece com outras var
parâmetros. A restrição é que você precisa passar uma referência para uma variável (não uma expressão
geral) e que o tipo de referência deve corresponder exatamente ao tipo de parâmetro.

· Finalmente existe uma opção bastante desconhecida para passar um objeto como parâmetro, chamada
referência constante e escrita como [ref] const. Quando um parâmetro é passado como referência constante
ele se comporta de forma semelhante a uma passagem por referência (var), mas permite mais flexibilidade no
tipo do parâmetro que você está passando, não exigindo uma correspondência exata de tipo (como passar um
objeto de uma subclasse é permitido).

Dicas de gerenciamento de memória


O gerenciamento de memória em Object Pascal está sujeito a três regras simples: você deve criar cada objeto e
alocar cada bloco de memória necessário, você deve destruir cada objeto e bloco de memória que criar e
alocar e deve destruir cada objeto apenas uma vez. Object Pascal suporta três tipos de gerenciamento de
memória para elementos dinâmicos (isto é, elementos que não estão na pilha e na área de memória global),
detalhados nesta parte restante desta seção:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

360 - 13: objetos e memória

· Cada vez que você cria um objeto, você também deve destruí-lo. Se você não fizer isso, a memória usada
por aquele objeto não será liberada para outros objetos, até que o programa termine.

· Ao criar um componente, você pode especificar um componente proprietário, passando o proprietário


para o construtor do componente. O componente proprietário (geralmente um formulário ou módulo de
dados) torna-se responsável por destruir todos os objetos que possui, o que faz automaticamente
quando é destruído. Em outras palavras, quando você destrói o formulário ou módulo de dados, ele
destrói todos os componentes que possui. Portanto, se você criar um componente e atribuir a ele um
proprietário, não precisará se preocupar em destruí-lo — mas ainda poderá decidir destruí-lo mais cedo,
graças ao mecanismo de notificação de destruição fornecido pela classe TComponent .

· Quando você aloca memória para strings, arrays dinâmicos e objetos referenciados por variáveis de
interface (conforme discutido no Capítulo 11), o Object Pascal libera automaticamente a memória
quando a referência sai do escopo. Você não precisa liberar uma string porque, quando ela se torna
inacessível, sua memória é liberada automaticamente. Caso precise liberá-lo mais cedo para liberar
espaço na memória, você pode atribuir a string
ou variável de matriz dinâmica como nula ou atribua uma string vazia à variável de string.

Destruindo objetos que você cria


No cenário mais simples, você deve destruir os objetos temporários criados. Qualquer objeto não temporário
deve ter um proprietário, fazer parte de uma coleção ou ser referenciado por
alguma estrutura de dados, que é responsável por destruir o objeto no devido tempo.

O código usado para criar e destruir um objeto temporário geralmente é encapsulado em um bloco try-
finally , para que o objeto seja destruído mesmo que algo dê errado ao usá-lo:

MeuObj := TMyClass.Create;
tentar
MeuObj.DoAlguma coisa;
finalmente
MeuObj.Free;
fim;

Outro cenário comum é que um objeto seja utilizado por outro, que passa a ser seu
proprietário:

construtor TMyOwner.Create;
começar
FSubObj := TSubObject.Create;
fim;

destruidor TMyOnwer.Destroy;
começar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória - 361

FSubObj.Free;
fim;

Existem alguns cenários comuns mais complexos, caso o assunto não seja criado até ser necessário
(inicialização lenta) ou possa ser destruído antes do proprietário, caso não seja mais necessário.

Para implementar a inicialização lenta, você não cria o assunto no construtor do proprietário, mas quando
for necessário:

função TMyOwner.GetSubObject: TSubObject


começar
se não for atribuído (FSubObj), então
FSubObj := TSubObject.Create;
Resultado:=FSubObj;
fim;

destruidor TMyOnwer.Destroy;
começar
FSubObj.Free;
fim;

Observe que o que foi dito acima depende do fato de que a memória de um objeto é inicializada quando
ele é criado pela primeira vez, o que significa que FSubObj será nulo. Observe também que você não
precisa testar se o objeto está atribuído antes de liberá-lo, porque é exatamente isso que Free faz, como
veremos na próxima seção.

Destruindo objetos apenas uma vez


Outro problema é que se você chamar o destruidor de um objeto duas vezes, receberá um erro. Um
destruidor é um método que desaloca a memória de um objeto. Podemos escrever código para um
destruidor, geralmente substituindo o destruidor Destroy padrão , para permitir que o objeto execute
algum código antes de ser destruído.

Destroy é um destruidor virtual da classe TObject . A maioria das classes que exigem código de
limpeza personalizado quando os objetos são destruídos substituem esse método virtual. A
razão pela qual você nunca deve definir um novo destruidor é que os objetos geralmente são
destruídos chamando o método Free , e esse método chama o destruidor virtual Destroy
(possivelmente a versão substituída) para você.
Como acabei de mencionar, Free é simplesmente um método da classe TObject , herdado por todas as
outras classes. O método Free basicamente verifica se o objeto atual (Self) não é nulo antes de chamar
o destruidor virtual Destroy .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

362 - 13: objetos e memória

note Você pode estar se perguntando por que pode chamar Free com segurança se a referência do objeto for nula, mas não pode chamar
Destruir. A razão é que Free é um método conhecido em um determinado local de memória, enquanto
a função virtual Destroy é determinada em tempo de execução observando o tipo do objeto,
uma operação muito perigosa se o objeto não existir mais.

Aqui está seu pseudocódigo gratuitamente :

procedimento TObject.Free;
começar
se Self <> nulo então
Destruir;
fim;

A seguir, podemos voltar nossa atenção para a função Assigned . Quando passamos um ponteiro para
esta função, ela simplesmente testa se o ponteiro é nulo. Portanto, as duas afirmações a seguir
são equivalentes, pelo menos na maioria dos casos:

se atribuído (MyObj) então


...
se MyObj <> nulo então
...

Observe que essas instruções testam apenas se o ponteiro não é nulo; eles não verificam se é um ponteiro
válido. Se você escrever o seguinte código

MeuObj.Free;
se MyObj <> nulo então
MeuObj.DoAlguma coisa;

o teste será avaliado como True e você receberá um erro na linha com a chamada ao método do
objeto. É importante perceber que chamar Free não define o valor de um objeto
referência a zero.

Não é possível definir automaticamente um objeto como nulo . Você pode ter diversas referências ao
mesmo objeto e o Object Pascal não as rastreia. Ao mesmo tempo, dentro de um método (como
Free) , podemos operar no objeto, mas não sabemos nada sobre a referência do objeto – o endereço
de memória da variável que usamos para chamar o método.

Ou seja, dentro do método Free ou qualquer outro método de uma classe, sabemos o endereço de
memória do objeto (Self), mas não sabemos a localização de memória da variável referente ao objeto,
como MyObj. Portanto, o método Free não pode afetar a variável MyObj .

Entretanto, quando chamamos uma função externa passando um objeto como parâmetro de referência, a
função pode então optar por modificar a referência original do objeto. Isso é exatamente o que faz
o procedimento pronto para uso FreeAndNil , um procedimento que podemos usar no lugar de Free e
depois definir a variável de referência como nil. Aqui está o código atual para FreeAndNil:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória - 363

procedimento FreeAndNil(const [ref] Obj: TObject); em linha;


era
Temperatura: TObject;
começar
Temperatura := Obj;
TObject(Ponteiro(@Obj)^) := nil;
Temp.Livre;
fim;

No passado, o parâmetro era apenas um ponteiro, mas a desvantagem era que você poderia passar,
para o procedimento FreeAndNil , um ponteiro bruto, referências de interface e outras estruturas de dados
incompatíveis. Isso geralmente causava corrupção de memória e bugs difíceis de encontrar. A partir do Delphi
10.4 o código foi modificado conforme mostrado acima, utilizando um parâmetro de referência const do
tipo TObject , limitando os parâmetros a objetos.

note Alguns especialistas em Delphi argumentariam que FreeAndNil nunca deveria ser usado, porque a visibilidade da
variável referente a um objeto deveria corresponder ao seu tempo de vida. Se um objeto possui outro e frees
está no destruidor, não há necessidade de definir a referência como nil pois faz parte de um objeto que você não
vai mais usar. Da mesma forma, uma variável local com um bloco try-finalmente liberando-a não precisa defini-
la como nil , pois está prestes a sair do escopo.

Como observação lateral, além do método Free , TObject também possui um método DisposeOf que é uma sobra
do suporte ARC que a linguagem teve por alguns anos. Atualmente, o método DisposeOf apenas
chama Free.

Para resumir o uso dessas operações de limpeza de memória, aqui estão algumas diretrizes:

· Sempre chame Free para destruir objetos, em vez de chamar o destruidor Destroy .

· Use FreeAndNil ou defina referências de objeto como nulas após chamar Free, a menos que a referência
A influência sairá do escopo imediatamente depois.

Gerenciamento de memória e interfaces


No Capítulo 11, apresentei os principais elementos do gerenciamento de memória para interfaces, que,
diferentemente dos objetos, são gerenciados e as referências são contadas. Como mencionei, as referências
de interface aumentam a contagem de referências do objeto referenciado, mas você pode declarar um
referência de interface como fraca para desabilitar a contagem de referência (mas ainda assim pedir ao
compilador para gerenciar a referência para você) ou você pode usar o modificador inseguro para desabilitar
completamente qualquer suporte do compilador para a referência específica. Nesta seção iremos um pouco mais fundo
nesta área, mostrando alguns exemplos adicionais ao que foi fornecido no Capítulo 11.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

364 - 13: objetos e memória

Mais sobre referências fracas


Um problema com os modelos de contagem de referências que o Delphi usa para interfaces é que,
se dois objetos se referem um ao outro, eles formam uma referência circular e sua contagem de
referências basicamente nunca chegará a zero. Referências fracas oferecem um mecanismo
para quebrar esses ciclos, permitindo definir uma referência que não aumente a contagem de referências.

Suponha que duas interfaces se referem uma à outra usando um de seus campos e um campo externo.
variável refere-se à primeira. A contagem de referência do primeiro objeto será 2 (o externo

Figura 13.4:
Referências entre
objetos podem formar ciclos,
algo que as
referências fracas explicam

variável e o campo do segundo objeto), enquanto a contagem de referência do segundo objeto é 1


(o campo do primeiro objeto). A Figura 13.4 ilustra esse cenário.

Agora, à medida que a variável externa sai do escopo, as contagens de referência dos dois objetos
são 1; e, como o objeto principal, Objeto1, que possui o Objeto2, não possui proprietário externo,
os objetos permanecerão na memória indefinidamente. Para resolver este tipo de situação, você
deve quebrar as referências circulares, algo longe de ser simples, visto que você não sabe quando
realizar esta operação (ela deve ser realizada quando a última referência externa sair do escopo,
fato dos quais os objetos não têm conhecimento).

A solução para esta situação, e para muitos cenários semelhantes, é usar uma referência fraca.
Conforme mencionado, uma referência fraca é uma referência a um objeto que não aumenta sua
contagem de referências. Tecnicamente, você define uma referência fraca aplicando o atributo [fraco]
para isso.

note Os atributos são um recurso avançado da linguagem Object Pascal abordado no Capítulo 16. Basta dizer que
eles são uma forma de adicionar algumas informações de tempo de execução sobre um símbolo, para que o código externo possa
determinar como lidar com ele.

Dado o cenário anterior, se a referência do segundo objeto de volta ao primeiro for uma referência
fraca (ver Figura 13.5), à medida que a variável externa sai do escopo, ambos os objetos serão
destruídos.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 365

Figura 13.5: O ciclo


de referências na
Figura 13.4, quebrado
usando uma referência
fraca (linha pontilhada)

Vejamos esta situação simples no código. Primeiramente, o exemplo da aplicação


ArcExperiments declara duas interfaces, uma referente à outra:

tipo
IMySimpleInterface = interface
'{B6AB548A-55A1-4D0F-A2C5-726733C33708}' ]
[procedimento DoSomething(BRaise: Boolean = False);
função RefCount: Inteiro;
fim;

IMyComplexInterface = interface
'{5E8F7B29-3270-44FC-B0FC-A69498DA4C20}' ]
[função GetSimple: IMySimpleInterface;
função RefCount: Inteiro;
fim;

O código do programa define duas classes diferentes que implementam as interfaces.


Observe como as referências cruzadas (FOwnedBy e FSimple são baseadas em interfaces
e uma das duas é definida como fraca):

tipo
TMySimpleClass = classe(TInterfacedObject, IMySimpleInterface)
privado
[Fraco] FOwnedBy: IMyComplexInterface;
público
construtor Create(Proprietário: IMyComplexInterface);
destruidor Destruir; sobrepor;
procedimento DoSomething(BRaise: Boolean = False);
função RefCount: Inteiro;
fim;

TMyComplexClass = classe(TInterfacedObject, IMyComplexInterface)


privado
FSimple: IMySimpleInterface;
público
construtor Criar;
destruidor Destruir; sobrepor;
função GetSimple: IMySimpleInterface;
função RefCount: Inteiro;
fim;

Aqui o construtor da classe “complexa” cria um objeto da outra classe:

construtor TMyComplexClass.Create;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

366 - 13: objetos e memória

começar
herdado Criar;
FSimple := TMySimpleClass.Create(Self);
fim;

Lembre-se que o campo FOwnedBy é uma referência fraca, portanto não aumenta a contagem de
referências do objeto ao qual se refere, neste caso o objeto atual (Self). Dada esta estrutura de código,
podemos escrever:

procedimento de classe TMyComplexClass.CreateOnly;


era
MeuComplexo: IMyComplexInterface;
começar
MeuComplexo := TMyComplexClass.Create;
MeuComplex.FSimple.DoSomething;
fim;

Isso não causará vazamento de memória, desde que a referência fraca seja usada. Por exemplo, com
código como:

era
MeuComplexo: IMyComplexInterface;
começar
MeuComplexo := TMyComplexClass.Create;
'
Log('Complex = + MyComplex.RefCount.ToString);
MyComplex.GetSimple.DoSomething(Falso);

Dado que cada construtor e destruidor registra sua execução, você obterá um log como:

Classe complexa criada


Classe simples criada
Complexo = 1
Aula simples fazendo algo
Classe complexa destruída
Classe simples destruída

Se você remover o atributo fraco do código, verá um vazamento de memória e também (na execução do
código acima) um valor para a contagem de referência de 2 em vez de 1.

Referências fracas são gerenciadas


Um elemento muito importante é que as referências fracas sejam gerenciadas. Em outras palavras,
o sistema mantém uma lista das referências fracas na memória e, quando um objeto é destruído,
verifica se há alguma referência fraca referente a ele e, se houver, atribui as referências reais
a ele. nulo, isto é, os ponteiros de referência externos reais serão iguais a zero.
Isso significa que referências fracas têm um custo de tempo de execução.

O bom de ter gerenciado referências fracas, em comparação com as tradicionais, é que você pode
verificar se uma referência de interface ainda é válida ou não (ou seja, o objeto ao qual ela se refere foi
destruído). Mas isso significa que, quando você estiver usando uma referência fraca, você deve sempre
testar se ela está atribuída antes de usá-la.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 367

No exemplo da aplicação ArcExperiments , o formulário possui um campo privado do tipo


Tipo IMySimpleInterface , declarado como uma referência fraca:

privado
[fraco] MySimple: IMySimpleInterface;

Existe também um botão atribuindo uma referência a esse campo, e outro diferente utilizando-o, após
certificar-se de que ainda é válido:

procedimento TForm3.BtnGetWeakClick(Remetente: TObject);


era
MeuComplexo: IMyComplexInterface;
começar
MeuComplexo := TMyComplexClass.Create;
MyComplex.GetSimple.DoSomething(Falso);
MeuSimples := MeuComplex.GetSimple;
fim;

procedimento TForm3.BtnUseWeakClick(Sender: TObject);


começar
se atribuído (MySimple) então
MySimple.DoSomething(Falso)
outro
'Nenhuma referência fraca' );
Registro(fim;

A menos que você modifique o código, o teste "if Assigned" falhará porque o manipulador de eventos
do primeiro botão cria e libera imediatamente os objetos, de modo que a referência fraca se torna nula
(pois agora é inválida). Mas, como é gerenciado, o compilador ajuda a rastrear seu status real (ao
contrário de uma referência a um objeto).

O atributo inseguro

Existem algumas circunstâncias muito específicas (por exemplo, durante a criação de uma instância)
nas quais uma função pode retornar um objeto com uma contagem de referências definida como zero.
Neste caso, para evitar que o compilador exclua o objeto imediatamente (antes que ele tenha a chance
de ser atribuído a uma variável, o que aumentaria sua contagem de referências para 1), nós
tem que marcar o objeto como inseguro.

A implicação aqui é que sua contagem de referências deve ser temporariamente ignorada, para tornar
o código seguro. Esse comportamento é conseguido usando um novo atributo específico,
[Inseguro], um recurso que você deve precisar apenas em circunstâncias muito específicas.

Esta é a sintaxe:

era
[Inseguro] Intf1: IInterface;

[Resultado: Inseguro] função GetIntf: IInterface;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

368 - 13: objetos e memória

O uso deste atributo pode fazer sentido ao implementar padrões de construção, como o padrão de
fábrica, em bibliotecas de uso geral.

note Para suportar o modelo de memória ARC agora obsoleto, a unidade do sistema usou uma diretiva insegura ,
porque não poderia usar o atributo antes de sua definição (posteriormente na mesma unidade). Isso não deve
ser usado em nenhum código fora dessa unidade e não é mais usado (você pode ver isso em um $IFDEF
diretiva).

Rastreando e verificando memória


Neste capítulo vimos os fundamentos do gerenciamento de memória em Object Pascal.
Na maioria dos casos, apenas aplicar as regras destacadas aqui será suficiente para manter seus
programas estáveis, evitar o uso excessivo de memória e basicamente permitir que você esqueça a memória.
gerenciamento. Existem outras práticas recomendadas para escrever aplicativos robustos, abordadas
posteriormente neste capítulo.

Nesta seção estou me concentrando nas técnicas que você pode usar para rastrear o uso de
memória, monitorar situações anormais e encontrar vazamentos de memória. Este é um conhecimento
importante para um desenvolvedor, mesmo que não seja estritamente parte da linguagem, mas mais
do suporte ao tempo de execução. Além disso, a implementação do gerenciador de memória depende da plataforma alvo
e sistema operacional, e você pode até conectar um gerenciador de memória personalizado em um
aplicativo Object Pascal (embora isso não seja comum).

Observe que toda a discussão relacionada ao rastreamento do status da memória, aos gerenciadores
de memória e à detecção de vazamentos se refere apenas à memória heap. A pilha e a memória
global são gerenciadas de maneira diferente e você basicamente não tem poder para intervir, mas
essas também são áreas de memória que raramente causam problemas.

Estado da memória
E quanto ao rastreamento do status da memória heap? O RTL oferece algumas funções úteis,
GetMemoryManagerState e GetMemoryMap. Embora o estado do gerenciador de memória seja uma
indicação do número de blocos alocados de vários tamanhos, o mapa de heap é bastante bom
pois representa o status da memória do aplicativo no nível do sistema.

Você pode examinar o status real de cada bloco de memória escrevendo um código como:

para I := Baixo(AMemoryMap) para Alto(AMemoryMap) faça


começar
caso AMemoryMap[I] de
csNão alocado: ...
csAlocado: ...

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 369

csReservado: ...
csSysAlocado: ...
csSysReservado: ...
fim;
fim;

Este código é usado no exemplo ShowMemory para criar uma representação gráfica do status da memória do
aplicativo.

RápidoMM4

Na plataforma Windows, o atual gerenciador de memória Object Pascal é chamado FastMM4 e foi
desenvolvido como um projeto de código aberto principalmente por Pierre La Riche.
Em outras plataformas, o Delphi utiliza o gerenciador de memória nativo da plataforma.

FastMM4 otimiza a alocação de memória, acelerando-a e liberando mais RAM para uso posterior. FastMM4
é capaz de fazer verificações extensivas de memória em
limpeza de memória, no uso incorreto de objetos excluídos, incluindo acesso baseado em interface
para esses dados, em sobregravações de memória e em saturação de buffer. Ele também pode fornecer
algum feedback sobre objetos restantes, ajudando a rastrear vazamentos de memória.

Alguns dos recursos mais avançados do FastMM4 estão disponíveis apenas na versão completa da biblioteca
(abordada na seção “Buffer Overruns no FastMM4 completo”), não na versão incluída no RTL padrão. É por
isso que se você quiser ter os recursos da versão completa, você deve baixar o código-fonte completo em:

https://github.com/pleriche/FastMM4

note Há uma nova versão desta biblioteca chamada FastMM5, que foi otimizada especificamente para
aplicativos multithread e pode funcionar muito melhor em grandes sistemas multinúcleo. A nova versão da
biblioteca está disponível com licença GPL (para projetos de código aberto) ou com licença comercial paga.
licença (vale totalmente o custo) para qualquer outra pessoa. Mais informações estão disponíveis no
leia-me do projeto em https://github.com/pleriche/FastMM5.

Rastreamento de vazamentos e outras configurações globais


A versão RTL do FastMM4 pode ser ajustada usando configurações globais na unidade do sistema .
Observe que, embora as declarações globais relevantes estejam na unidade System , o gerenciador de
memória real é implementado no arquivo de código-fonte RTL getmem.inc .

A configuração mais fácil de usar é a variável global ReportMemoryLeaksOnShutdown , que permite rastrear
facilmente um vazamento de memória. Você precisa habilitá-lo no início da execução do programa. Então,
quando o programa termina, ele informa se há algum vazamento de memória no seu código (ou em qualquer
uma das bibliotecas que você está usando).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

370 - 13: objetos e memória

note Configurações mais avançadas do gerenciador de memória incluem NeverSleepOnMMThreadContention


variável global para alocações multithread; as funções GetMinimumBlockAlignment e Set-MinimumBlockAlignment, que
podem acelerar algumas operações SSE às custas do uso de mais memória; e a capacidade de registrar um
vazamento de memória esperado chamando o procedimento global RegisterExpectedMemoryLeak.

Para demonstrar o relatório e registro padrão de vazamento de memória, escrevi o exemplo simples do
LeakTest . Possui um botão com este manipulador OnClick :

era
P: Ponteiro;
começar
ObterMem(P, 100); fim; // Vazamento de memória!

Este código aloca 100 bytes perdidos ou vazados. Se você executar o programa LeakTest de dentro do
IDE e pressionar o primeiro botão uma vez; então, ao fechar o programa, você receberá uma
mensagem como a da parte superior da Figura 13.6.

O outro “vazamento” do programa é causado pela criação e permanência na memória de um TBut-


ton, mas como esse objeto inclui muitos subelementos, o relatório de vazamento se torna mais
complexo, como o da parte inferior da Figura 13.6. Ainda assim, temos algumas informações limitadas
sobre o vazamento em si.

Figura 13.6: Os
vazamentos de memória
relatados pelo gerenciador de memória
no Windows após o
encerramento do
aplicativo LeakTest

O programa também aloca alguma memória para um ponteiro global que nunca será liberado, mas ao
registrar esse vazamento potencial conforme esperado, ele não será relatado:

procedimento TFormLeakTest.FormCreate(Sender: TObject);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 371

começar
GetMem(GlobalPointer, 200);
RegisterExpectedMemoryLeak(GlobalPointer);
fim;

Novamente, este relatório básico de vazamento está disponível apenas na plataforma Windows, onde
FastMM4 é usado por padrão.

Estouros de buffer no FastMM4 completo

Este é um tópico bastante avançado e específico da plataforma Windows, portanto, recomendo


que apenas os desenvolvedores mais experientes leiam esta seção.

Se você quiser ter mais controle sobre relatórios de vazamentos (como ativar o registro baseado em arquivo),
para ajustar a estratégia de alocação e usar as verificações de memória fornecidas pelo
FastMM4, você precisa baixar a versão completa. Isso consiste no arquivo FastMM4.pas mais o
arquivo de configuração FastMM4Options.inc.

É o último arquivo que você precisa editar para ajustar as configurações, simplesmente comentando e
descomentando um grande número de diretivas. Por convenção, isso é feito colocando
um ponto antes da instrução $DEFINE , transformando-o em um comentário simples, como na primeira destas
duas linhas retiradas do arquivo de inclusão:

{.$DEFINE Align16Bytes} // Comentário


{$DEFINE UseCustomFixedSizeMoveRoutines} // Configuração ativa

Para esta demonstração, ativei as seguintes configurações relevantes, relatadas aqui para dar uma ideia dos
tipos de definições disponíveis:

{$DEFINE FullDebugMode} {$DEFINE LogErrorsToFile} {$DEFINE EnableMemoryLeakReporting} {$DEFINE HideExpectedLeaksRegisteredByPointer} {$DEFINE RequireDebuggerPresenceForLeakReporting}

O programa de teste (na pasta FastMMCode, que também inclui o código-fonte completo da versão do
FastMM4 que utilizei, para sua comodidade) ativa a versão customizada do gerenciador de memória no arquivo
de código-fonte do projeto, configurando-o como a primeira unidade :

programa FastMMCode;
usa
FastMM4 em 'FastMM4.pas',
Formulários,
FastMMForm em 'FastMMForm.pas'; {Formulário 1}

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

372 - 13: objetos e memória

Você também precisará de uma cópia local do arquivo FastMM_FullDebugMode.dll para que funcione.
Este programa de demonstração causa uma saturação do buffer ao obter mais texto do que cabe no
buffer local, pois Length(Caption) é maior que os 5 caracteres fornecidos:

procedimento TForm1.Button2Click(Remetente: TObject);


era
PCh1: PChar;
começar
GetMem(PCh1, 5);
GetWindowText(Handle, PCh1, Comprimento(Caption));
MostrarMensagem(PCh1);
FreeMem(PCh1); fim;

O gerenciador de memória aloca bytes extras no início e no final de cada bloco de memória com valores especiais e
verifica esses valores quando você libera cada bloco de memória. É por isso que você recebe o erro na chamada do
FreeMem . Ao pressionar o botão (no depurador), você verá uma mensagem de erro muito longa, que também é registrada
no arquivo:

FastMMCode_MemoryManager_EventLog.txt

Esta é a saída do erro de saturação com rastreamentos de pilha no momento das operações de alocação e
liberação, além do rastreamento de pilha atual e um despejo de memória (parcial aqui):

FastMM detectou um erro durante uma operação FreeMem. O rodapé do bloco foi corrompido.

O tamanho do bloco é: 5

Rastreamento de pilha de quando este bloco foi alocado (endereços de retorno): 40305E [System]
[System.@GetMem]
44091A [Controles][Controls.TControl.Click]
44431B [Controles][Controls.TWinControl.WndProc]
42D959 [StdCtrls][StdCtrls.TButtonControl.WndProc]
44446C [Controles][Controls.DoControlMsg]
44431B [Controles][Controls.TWinControl.WndProc]
45498A [Formulários][Forms.TCustomForm.WndProc]
443A43 [Controles][Controls.TWinControl.MainWndProc]
41F31A [Classes][Classes.StdWndProc]
76281A10 [GetMessageW]

O bloco é usado atualmente para um objeto da classe: Desconhecido

O número de alocação é: 381

Rastreamento de pilha de quando o bloco foi liberado anteriormente (endereços de retorno): 40307A [System]
[System.@FreeMem]
42DB8A [StdCtrls][StdCtrls.TButton.CreateWnd]
443863 [Controles][Controls.TWinControl.UpdateShowing]
44392B [Controles][Controls.TWinControl.UpdateControlState]
44431B [Controles][Controls.TWinControl.WndProc]
45498A [Formulários][Forms.TCustomForm.WndProc]

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 373

44009F [Controles][Controls.TControl.Perform]
43ECDF [Controles][Controls.TControl.SetVisible]
45F770
76743833 [BaseThreadInitThunk]

O rastreamento de pilha atual que leva a este erro (endereços de retorno):


40307A [Sistema][Sistema.@FreeMem]
44091A [Controles][Controls.TControl.Click]
44431B [Controles][Controls.TWinControl.WndProc]
42D959 [StdCtrls][StdCtrls.TButtonControl.WndProc]
44446C [Controles][Controls.DoControlMsg]
44431B [Controles][Controls.TWinControl.WndProc]
45498A [Formulários][Forms.TCustomForm.WndProc]
443A43 [Controles][Controls.TWinControl.MainWndProc]
41F31A [Classes][Classes.StdWndProc]
76281A10 [GetMessageW]

Despejo de memória atual de 256 bytes começando no endereço do ponteiro 133DEF8:


46 61 73 74 4D 4D 43 6F 64 [... omitido...]

Não que isso seja extremamente óbvio, mas deve fornecer informações suficientes para você começar a perseguir
o bug.

Observe que sem essas configurações no gerenciador de memória, você basicamente não verá nenhum erro e
o programa continuará em execução - embora você possa enfrentar bugs aleatórios caso a saturação do buffer
afete uma área da memória na qual algo mais está armazenado. Nesse ponto, você pode obter alguns erros
estranhos e muito difíceis de rastrear.

Por exemplo, uma vez vi a substituição parcial da parte inicial dos dados de um objeto, onde a referência de classe
está armazenada. Através dessa corrupção de memória, a classe tornou-se indefinida e toda e qualquer chamada
para uma de suas funções virtuais travaria gravemente – algo muito difícil de relacionar com uma operação de
gravação de memória em uma área totalmente diferente do programa.

Gerenciamento de memória em plataformas diferentes de


janelas

Considerando como o gerenciamento de memória funciona em compiladores Object Pascal, vale a pena considerar
algumas das opções que você tem para garantir que tudo esteja sob controle em outras plataformas. Antes de
prosseguirmos, é importante observar que, em plataformas não Windows, o Delphi não usa o gerenciador de
memória FastMM4, portanto, definir o sinalizador global ReportMemoryLeaksOnShutdown para
verificar vazamentos de memória quando o programa fecha é inútil. Há também outra razão, que é que
geralmente não há um
maneira de fechar um aplicativo no celular, determinados aplicativos permanecem na memória até serem
removidos à força pelo usuário ou pelo sistema operacional.

Nas plataformas macOS, iOS e Android, o Object Pascal RTL chama diretamente o malloc
e funções gratuitas da biblioteca libc nativa . Uma maneira de monitorar o uso de memória em

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

374 - 13: objetos e memória

esta plataforma dependerá de ferramentas de plataforma externa. Por exemplo, em iOS (e macOS)
você pode usar a ferramenta Instruments da Apple, que é um sistema de rastreamento completo que monitora
todos os aspectos de seus aplicativos em execução em um dispositivo físico.

Acompanhamento de alocações por classe


Finalmente, existe uma abordagem Object Pascal para rastrear uma classe específica, em vez de
gerenciar a memória em geral. A alocação de memória para um objeto, na verdade, ocorre
chamando o método de classe virtual NewInstance , enquanto a limpeza é feita pelo método virtual
FreeInstance . Esses são métodos virtuais que você pode substituir para uma determinada classe para
personalizar a estratégia específica de alocação de memória.

A vantagem é que você pode fazer isso independentemente dos construtores (já que você pode ter mais de
um) e do destruidor, separando claramente o código de rastreamento de memória do código padrão de
inicialização e finalização do objeto.

Embora este seja um caso extremo (provavelmente vale a pena fazer apenas para algumas estruturas de
memória grandes), você pode substituir esses métodos para contar o número de objetos de uma determinada
classe que são criados e destruídos, calcular o número de instâncias ativas e, pelo menos ao final, verifique
se a contagem chega a zero conforme o esperado.

Escrevendo aplicativos robustos


Neste capítulo, e em muitos capítulos anteriores desta seção, abordei algumas técnicas focadas na escrita
de aplicações robustas e no gerenciamento adequado da alocação e desalocação de memória.

Nesta seção final de um capítulo focado no gerenciamento de memória, decidi listar alguns tópicos um
pouco mais avançados, que ampliam a cobertura anterior. Mesmo que o uso de blocos try-finalmente e
destruidores de chamada já tenha sido abordado, os cenários destacados aqui são um pouco mais
complexos e envolvem o uso conjunto de vários recursos de linguagem.

Esta não é realmente uma seção avançada, mas algo que todos os desenvolvedores Object Pascal
deveriam realmente dominar para serem capazes de escrever aplicações robustas. Apenas a última
subseção sobre ponteiros e referências a objetos é definitivamente mais avançada em escopo, pois se
aprofunda na estrutura interna da memória de um objeto e em uma referência de classe.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 375

Construtores, Destruidores e Exceções


Construtores e destruidores são ferramentas poderosas para o gerenciamento correto da memória, mas também
podem ser uma fonte de problemas se não forem usados corretamente. Os construtores virtuais quase
sempre devem chamar primeiro o construtor de seus pais (por meio de uma chamada herdada ). Os
destruidores geralmente devem chamar o destruidor de seus pais por último (novamente, com uma chamada herdada ).

note Para seguir o que é uma boa prática de codificação, você geralmente deve adicionar uma chamada de construtor de
classe base em cada construtor do seu código Object Pascal, mesmo que isso não seja obrigatório e a chamada
extra possa ser inútil (como ao herdar TObject.Create) .

Nesta seção quero focar especificamente no que acontece quando um construtor falha em um
cenário clássico como:

MeuObj := TMyClass.Create;
tentar
MeuObj.DoAlguma coisa;
finalmente
MeuObj.Free;
fim;

Se o objeto for criado e atribuído à variável MyObj , o bloco final cuidará de


destruindo-o. Mas se a chamada Create gerar uma exceção, o bloco try-finalmente não será inserido, o que é
correto. Quando um construtor gera uma exceção, o código destruidor correspondente é executado
automaticamente no que pode ser um objeto parcialmente inicializado. Se o construtor criar dois subobjetos,
por exemplo, eles precisarão ser limpos invocando o destruidor correspondente. No entanto, isso pode levar a
problemas potenciais se estiver no destruidor. você assume que o objeto foi inicializado corretamente.

Isso não é simples de entender na teoria, então vamos dar uma olhada em uma demonstração prática em código. O
O exemplo SafeCode contém uma classe com um construtor e um destruidor que geralmente estará correto – a
menos que o próprio construtor falhe:

tipo
TUnsafeDestructor = classe
privado
Lista FL: ListaTL;
público
construtor Criar (PositiveNumber: Integer);
destruidor Destruir; sobrepor;
fim;

construtor TUnsafeDestructor.Create(PositiveNumber: Integer);


começar
herdado Criar;

se NúmeroPositivo <= 0 então


aumentar Exceção.Create( 'Não a número positivo' );
FList := TList.Create;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

376 - 13: objetos e memória

fim;

destruidor TUnsafeDestructor.Destroy;
começar
FList.Limpar;
FList.Free;
herdado;
fim;

O problema não está nos casos em que o objeto foi totalmente criado, mas nos casos em que o
campo FList ainda está definido como nulo, o que aconteceria se o construtor recebesse
um número negativo. Nesse caso, o construtor falharia e o destruidor acionaria e tentaria invocar
a chamada Clear em FList, que terá sido inicializada como nil antes mesmo de o construtor ser
invocado. A tentativa de acessar Clear on nulo gerará uma exceção de “violação de acesso”.

A maneira segura de escrever o mesmo código é a seguinte:

destruidor TUnsafeDestructor.Destroy;
começar
se atribuído (FList) então
FList.Limpar;
FList.Free;
herdado;
fim;

A moral da história, novamente, é que, em um destruidor, nunca tome como certo que o construtor
correspondente inicializou completamente o objeto. Você pode fazer essa suposição para qualquer
outro método, mas não para o destruidor.

Finalmente blocos aninhados


Finalmente, os blocos são provavelmente a técnica mais importante e comum para tornar seus
programas seguros. Eu não acho que este seja um tópico avançado, mas você usa finalmente em todo o
lugar ou não? E você o usa corretamente em casos de fronteira, como operações aninhadas, ou
combina várias instruções de finalização em um único bloco final? Este está longe de ser um
exemplo de código perfeito:

procedimento TForm1.BtnTryFClick(Remetente: TObject);


era
A1, A2: TAClass;
começar
A1 :=TAClass.Create;
A2 :=TAClass.Create;
tentar
A1.Tanto faz := 'Um' ;
A2.Tanto faz := 'Dois' ;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 377

finalmente
A2.Livre;
A1.Grátis;
fim;
fim;

Esta é uma versão mais segura e correta do mesmo código (extraído novamente do
Exemplo de SafeCode):

procedimento TForm1.BtnTryFClick(Remetente: TObject);


era
A1, A2: TAClass;
começar
A1 :=TAClass.Create;
tentar
A2 :=TAClass.Create;
tentar
A1.Tanto faz := 'Um' ;
A2.Tanto faz := 'Dois' ;
finalmente
A2.Livre;
fim;
finalmente
A1.Grátis;
fim;
fim;

Verificação dinâmica de tipo


As operações de conversão dinâmica entre tipos em geral e tipos de classe em particular são outra
possível fonte de armadilhas. Principalmente se você não usar os operadores is e as e simplesmente fizer
hard casts. Cada typecast direto é, de fato, uma fonte potencial de erro (a menos que siga uma verificação
is ).

A conversão de tipos de objetos para ponteiros, de e para referências de classe, de objetos para interfaces,
de e para strings é potencialmente muito perigosa, mas difícil de evitar em algumas circunstâncias
especiais. Por exemplo, você pode querer salvar a referência do objeto na propriedade Tag de um
componente, que é um número inteiro, para ser forçado a fazer hardcast.
Outro caso é quando você salva objetos em uma lista de ponteiros usando o método antigo
TList (em vez de uma lista genérica com segurança de tipo, abordada no próximo capítulo).

Este é um exemplo bastante estúpido:

procedimento TForm1.BtnCastClick(Remetente: TObject);


era
Lista: TList;
começar
Lista := TList.Create;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

378 - 13: objetos e memória

tentar
List.Add(Ponteiro(Remetente));
List.Add(Ponteiro(23422));
// Elenco direto
TButton(Lista[0]).Caption := 'Ai ';
TButton(List[1]).Caption := finalmente 'Ai' ;

Lista.Grátis;
fim;
fim;

A execução deste código geralmente causará uma violação de acesso.

note que escrevi geralmente porque quando você acessa a memória aleatoriamente, você nunca sabe o efeito real. No
Às vezes, os programas simplesmente sobrescrevem a memória sem causar um erro imediato, mas você terá
dificuldade em descobrir por que outros dados foram corrompidos.

Você deve evitar situações semelhantes sempre que possível, mas se não tiver outra alternativa, o que
pode fazer para corrigir esse código? A abordagem natural seria usar
um elenco as safe ou uma verificação de tipo is, como nos seguintes trechos:

// "como" elenco
(TObject(Lista[0]) como TButton).Caption := 'Ai' ;
(TObject(List[1]) como TButton).Caption := 'Ai' ;

// "é" elenco
se TObject(List[0]) for TButton então
TButton(List[0]).Caption := se 'Ai' ;
TObject(List[1]) for TButton então
TButton(Lista[1]).Caption := 'Ai' ;

No entanto, esta não é a solução, você continuará recebendo violações de acesso. O problema é que
ambos is e as acabam chamando TObject.InheritsFrom, uma operação difícil de realizar em um número!

A solução? A solução real é evitar situações semelhantes em primeiro lugar (esse tipo de código
honestamente faz pouco sentido), por exemplo, usando um TObjectList ou alguma outra técnica segura
(novamente, consulte o próximo capítulo para classes genéricas de contêineres). Se você realmente
gosta de hacks de baixo nível e gosta de brincar com ponteiros, você pode tentar descobrir se um
determinado “valor numérico” é realmente uma referência a um objeto ou não. Esta não é uma operação
trivial, no entanto. Há um lado interessante nisso, que tomo como desculpa para a demonstração a
seguir explicar a estrutura interna de um objeto e de uma referência de classe.

Este ponteiro é uma referência de objeto?


Esta seção explica a estrutura interna de objetos e referências de classe e vai muito além do
nível de discussão na maior parte deste livro. Ainda assim,

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

13: objetos e memória – 379

pode fornecer aos leitores mais experientes alguns insights interessantes, então decidi manter
este material que escrevi no passado para um artigo avançado sobre gerenciamento de memória.
Observe também que a implementação específica abaixo é realmente específica do Windows em
termos de verificações de memória.

Há momentos em que você tem ponteiros por perto (um ponteiro é apenas um valor numérico que se refere à
localização da memória física de alguns dados). Esses ponteiros podem, na verdade, ser referências a
objetos, e você geralmente sabe quando eles o são e os usa como tal. Mas toda vez que você faz uma conversão
de baixo nível você está prestes a estragar um programa inteiro. Existem técnicas para tornar esse tipo de
gerenciamento de ponteiros um pouco mais seguro, mesmo que não as garanta 100 por cento.

O ponto de partida que você pode querer considerar antes de trabalhar com um ponteiro é se
na verdade, é uma indicação legal ou não. A função Assigned apenas verifica se um ponteiro não é
nulo, o que não ajuda neste caso. No entanto, a função pouco conhecida FindHIn-stance do Object Pascal RTL
(na unidade System , disponível no Windows
plataforma) retorna o endereço base do bloco heap incluindo o objeto passado como parâmetro, ou zero se o
ponteiro se referir a uma página inválida (evitando
mas extremamente difícil rastrear erros de página de memória). Se você pegar um número quase
aleatoriamente, é provável que ele não se refira a uma página de memória válida.

Este é um bom ponto de partida, mas podemos fazer melhor, pois não ajudará se o valor for uma referência
de string ou qualquer outro ponteiro válido e não uma referência de objeto. Agora, como você sabe se um
ponteiro é realmente uma referência a um objeto? Eu criei o seguinte teste empírico. Os primeiros 4 bytes de
um objeto são o ponteiro para sua classe. Se considerarmos a estrutura de dados interna de uma
referência de classe, ela possui em sua posição vmtSelfPtr um ponteiro para si mesma. Isso é representado
aproximadamente na imagem da Figura 13.7.

Figura 13.7: Uma


representação
aproximada da estrutura
interna de objetos e
referências de classe

Em outras palavras, desreferenciando o valor em um local de memória vmtSelfPtr bytes do ponteiro de


referência de classe (este é um deslocamento negativo, menor na memória), você deve
obtenha o mesmo ponteiro de referência de classe novamente. Além disso, na estrutura de dados interna de um
referência de classe, você pode ler as informações de tamanho da instância (no vmtInstanceSize
posição) e veja se há um número razoável lá. Aqui está o código real:

função IsPointerToObject (Endereço: Ponteiro): Boolean;


era
ClassPointer, VmtPointer: PByte;
TamanhoInst: Inteiro;
começar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

380 - 13: objetos e memória

Resultado := Falso;
se FindHInstance(Endereço) > 0 então
começar
VmtPointer := PByte(Endereço^);
ClassPointer := VmtPointer + vmtSelfPtr;
se atribuído (VmtPointer) e
(FindHInstance(VmtPointer) > 0) então
começar
TamanhoInst := (PInteger(
VmtPointer + VmtInstanceSize))^;
// Verifique o auto-ponteiro e tamanho de instância "razoável"
if Ponteiro(Ponteiro(ClassPointer)^ =
Ponteiro(VmtPointer)) e
(InstSize > 0) e (InstSize < 10000) então
Resultado := Verdadeiro;
fim;
fim;
fim;

note Há uma probabilidade muito alta de que esta função retorne um valor correto, mas não 100% com certeza.
Infelizmente, pode acontecer que dados aleatórios na memória passem nos testes.

Tendo esta função em mãos, no exemplo anterior do SafeCode , podemos adicionar uma verificação de
ponteiro para objeto antes de fazer uma conversão segura:

se IsPointerToObject(Lista[0]) então
(TObject(Lista[0]) como TButton).Caption:= 'Ai' ;
se IsPointerToObject(Lista[1]) então
(TObject(Lista[1]) como TButton).Caption:= 'Ai' ;

A mesma ideia também pode ser aplicada diretamente a referências de classe para implementar
conversões seguras entre elas. Novamente, é melhor tentar evitar problemas semelhantes
escrevendo um código mais seguro e limpo, mas, caso você não possa evitá-lo, o método IsPointerToObject
função pode ser útil. De qualquer forma, esta seção deveria ter explicado um pouco
os internos dessas estruturas de dados do sistema.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

parte III: recursos avançados - 381

parte III: avançado


características

Agora que nos aprofundamos nos fundamentos da linguagem e no paradigma da programação orientada a objetos,
é hora de descobrir alguns dos recursos mais recentes e avançados da linguagem Object Pascal. Genéricos,
métodos anônimos e reflexão abrem-se para o desenvolvimento de código usando novos paradigmas que
estendem a programação orientada a objetos de maneiras significativas.

Na verdade, alguns desses recursos de linguagem mais avançados permitem que os desenvolvedores adotem
novas maneiras de escrever código, oferecendo ainda mais tipos e abstrações de código, além de permitir uma
abordagem mais dinâmica à codificação.

A última parte da seção expandirá esses recursos de linguagem, oferecendo uma visão geral dos principais
elementos da biblioteca de tempo de execução, que são tão essenciais para o modelo de
desenvolvimento Object Pascal que tornam a distinção entre linguagem e biblioteca bastante confusa. Iremos
inspecionar, por exemplo, a classe TObject que, como vimos anteriormente, é a base
class de todas as classes que você escreve e tem uma função muito proeminente para ser confinada a um detalhe
de implementação da biblioteca.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

382 - parte III: recursos avançados

Capítulos da Parte III


Capítulo 14: Genéricos
Capítulo 15: Métodos Anônimos
Capítulo 16: Reflexão e Atributos
Capítulo 17: A Classe TObject
Capítulo 18: A Biblioteca de Tempo de Execução

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 383

14: genéricos

A forte verificação de tipo fornecida pelo Object Pascal é útil para melhorar a correção do código, um
tópico que enfatizei bastante neste livro. A verificação forte de tipo, entretanto, também pode ser um
incômodo, já que você pode querer escrever um procedimento ou uma classe que possa agir de
forma semelhante em diferentes tipos de dados. Esse problema é resolvido por um recurso da
linguagem Object Pas-cal, também disponível em linguagens semelhantes como C# e Java, chamados genéricos

O conceito de classes genéricas ou de modelo, na verdade, vem da linguagem C++.


Isto é o que escrevi em 1994 em um livro sobre C++:

Você pode declarar uma classe sem especificar o tipo de um ou mais membros de dados:
esta operação pode ser adiada até que um objeto daquela classe seja realmente
declarado. Da mesma forma, você pode definir uma função sem especificar o tipo de um ou
mais de seus parâmetros até que a função seja chamada.

note O texto foi extraído do livro “Borland C++ 4.0 Object-Oriented Programming” que escrevi com
Steve Tendon no início dos anos 90.

Este capítulo se aprofunda no tema, começando pelos fundamentos, mas também cobrindo alguns
cenários de uso avançados e até mesmo indicando como os genéricos podem ser aplicados aos padrões.
programação visual.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

384 - 14: genéricos

Pares genéricos de valores-chave


Como primeiro exemplo de classe genérica, implementei uma estrutura de dados de par chave-valor.
O primeiro trecho de código abaixo mostra a estrutura de dados escrita de forma tradicional, com um
objeto usado para armazenar o valor:

tipo
TKeyValue = classe
privado
FKey: string;
FValor: TObject;
procedimento SetKey (valor const: string);
procedimento SetValue (valor const: TObject);
público
chave de propriedade: string lida FKey escreve SetKey;
valor da propriedade: TObject lê FValue escreve SetValue;
fim;

Para usar esta classe você pode criar um objeto, definir sua chave e valor e usá-lo, como nos seguintes
trechos de vários métodos do formulário principal do exemplo KeyValueClassic :

// Criar Formulário
Kv := TKeyValue.Create;

// Botão1Clique
Kv.Key := 'minhachave';
Kv.Valor := Remetente;

// Botão2Clique
Kv.Valor := Próprio; // O forma

// Botão3Clique
Mostrar mensagem( '[' + Kv.Chave + ',' + Kv.Valor.ClassName + ']' );

E se você precisar de uma classe semelhante contendo um número inteiro em vez de um objeto? Bem,
ou você faz uma conversão de tipo muito pouco natural (e perigosa) ou cria uma classe nova e separada
para conter uma chave de string com um valor numérico. Embora copiar e colar da classe original
possa parecer uma solução, você acaba com duas cópias de código que serão basicamente iguais e que
vão contra os bons princípios de programação e precisarão de um pesadelo de manutenção, pois você
terá que atualizar cada cópia com novos recursos ou
para corrigir os mesmos bugs em duas, três ou vinte cópias quase idênticas.

Os genéricos tornam possível usar uma definição muito mais ampla para o valor, escrevendo uma única
classe genérica. Depois de instanciar a classe genérica de valor-chave, ela se torna uma classe específica,
vinculada a um determinado tipo de dados. Portanto, você ainda terá duas, três ou vinte classes compiladas
em seu aplicativo, mas terá uma única definição de código-fonte para todas elas que lidam com verificação
de tipo nativa para tipo e sem sobrecarga adicional de tempo de execução.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 385

Mas estou me adiantando. Vamos começar com a sintaxe usada para definir a classe genérica para
nosso par chave-valor:

tipo
TKeyValue<T> = classe
privado
FKey: string;
Valor F: T;
procedimento SetKey (valor const: string);
procedimento SetValue (valor const: T);
público
chave de propriedade: string lida FKey escreve SetKey;
valor da propriedade: T read FValue write SetValue;
fim;

Nesta definição de classe, há um tipo não especificado, indicado pelo espaço reservado T, colocado
entre colchetes angulares. O símbolo T é freqüentemente usado por convenção, mas no que diz respeito
ao compilador você pode usar qualquer símbolo que desejar. Usar T geralmente torna o código mais
legível quando a classe genérica usa apenas um tipo paramétrico; em
caso a classe precise de múltiplos tipos paramétricos é comum nomeá-los de acordo com
sua função real, em vez de usar uma sequência de letras (T, U, V) como era feito em C++ durante
os primeiros dias.

note “T” tem sido o nome padrão, ou espaço reservado, para um tipo genérico desde os dias em que a linguagem C+
+ introduziu modelos no início dos anos 1990. Dependendo dos autores, o “T” significa “Tipo” ou “Tipo de
modelo”. Esta convenção funciona bem no mundo Delphi já que os tipos são em geral prefixados
com T, então faz sentido usar “T” para significar “Tipo”.

A classe genérica TKeyValue<T> usa o tipo não especificado como o tipo de um de seus dois campos,
o valor da propriedade e o parâmetro do método setter. Os métodos são definidos normalmente, mas
observe que, independente de terem a ver com o tipo genérico, sua definição contém o nome
completo da classe, incluindo o tipo genérico:

procedimento TKeyValue<T>.SetKey(valor const: string);


começar
FKey := Valor;
fim;

procedimento TKeyValue<T>.SetValue(const Valor: T);


começar
FValor := Valor;
fim;

Em vez disso, para usar a classe, você precisa qualificá-la totalmente, fornecendo o tipo real do tipo
genérico. Por exemplo, agora você pode declarar um objeto de valor-chave que hospeda botões como
valores escrevendo:

era
Kv: TKeyValue<TButton>;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

386 - 14: genéricos

O nome completo também é necessário ao criar uma instância, porque este é o tipo real
nome (enquanto o nome do tipo genérico e não instanciado é como um mecanismo de construção de tipo).

Usar um tipo específico de valor do par chave-valor torna o código muito mais robusto, pois agora você
só pode adicionar objetos TButton (ou derivados) ao par chave-valor e pode então acessar os vários métodos
e propriedades do extraído objeto.

Estes são alguns trechos do formulário principal do exemplo KeyValueGeneric :

// Criar Formulário
Kv := TKeyValue<TButton>.Create;

// Botão1Clique
Kv.Key := 'a minha chave' ;
Kv.Value := Remetente como TButton;

// Botão2Clique
Kv.Value := Remetente como TButton; // Era "Eu", mas isso é agora inválido!

// Botão3Clique
Mostrar mensagem ( '[' + Kv.Chave + ',' + Kv.Valor.Nome + ']' );

Ao atribuir um objeto genérico na versão anterior do código, poderíamos adicionar um botão ou um


formulário, agora só podemos adicionar botões, uma regra imposta pelo compilador. Da mesma forma,
em vez de um Kv.Value.ClassName genérico na saída, podemos usar o
Nome do componente ou qualquer outra propriedade da classe TButton .

Claro, também podemos imitar o programa original declarando o par chave-valor com um tipo de objeto,
como:

era
Exemplo: TKeyValue<TObject>;

Nesta versão da classe genérica de par chave-valor, podemos adicionar qualquer objeto como valor.
Entretanto, não seremos capazes de fazer muito nos objetos extraídos, a menos que os convertamos
para um tipo mais específico. Para encontrar um bom equilíbrio, você pode querer algo entre botões
específicos e qualquer objeto, solicitando que o valor seja um componente:

era
Kvc: TKeyValue<TComponent>;

Você pode ver trechos de código correspondentes no mesmo exemplo KeyValueGeneric .


Finalmente, também podemos criar uma instância da classe genérica de par chave-valor que não armazena
valores de objetos, mas sim números inteiros simples:

era
Kvi: TKeyValue<Inteiro>;
começar
Kvi := TKeyValue<Integer>.Create;
tentar
Kvi.Chave := 'Objeto' ;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 387

kvi.Valor := 100;
Kvi.Value := Esquerda;
'['
ShowMessage (+ Kvi.Key + IntToStr ',' +
(Kvi.Value) + ); ']'
finalmente
Kvi.Grátis;
fim;

Variáveis embutidas e inferência de tipos genéricos


Quando você declara uma variável de tipo genérico, a declaração pode ser bastante longa.
Ao criar um objeto desse tipo, você deve repetir a mesma declaração. Isto é, a menos que você aproveite
as declarações de variáveis embutidas e sua capacidade de inferir o tipo de variável. O último fragmento
de código acima pode, portanto, ser escrito como:

começar
var Kvi := TKeyValue<Integer>.Create;
tentar
...

Neste código você não precisa repetir a declaração completa do tipo genérico duas vezes. Isso é
particularmente útil ao usar contêineres, como veremos mais tarde.

Regras de tipo em genéricos


Quando você declara uma instância de um tipo genérico, esse tipo obtém uma versão específica, que é
imposta pelo compilador em todas as operações subsequentes. Então, se você tiver uma classe genérica
como:

tipo
TSimpleGeneric<T> = classe
Valor: T;
fim;

ao declarar um objeto específico com um determinado tipo, você não pode atribuir um tipo diferente ao
campo Valor . Dados os dois objetos a seguir, algumas das tarefas abaixo (parte do
o exemplo TypeCompRules ) estão incorretos:

era
Sg1: TSimpleGeneric<string>;
Sg2: TSimpleGeneric<Integer>;
começar
Sg1 := TSimpleGeneric<string>.Create;
Sg2 := TSimpleGeneric<Integer>.Create;

Sg1.Valor := 'Foo' // ;
Sg1.Valor := 10; Erro

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

388 - 14: genéricos

// E2010 Tipos incompatíveis: 'string' e 'Inteiro'

Cg2.Valor := 'Foo' ; // Erro


// E2010 Tipos incompatíveis: 'Inteiro' e 'corda'
Sg2.Valor := 10;

Depois de definir um tipo específico na declaração genérica, isso é imposto pelo compilador,
como seria de esperar de uma linguagem fortemente tipada como Object Pascal. A verificação
de tipo também existe para objetos genéricos como um todo. Ao especificar o parâmetro
genérico para um objeto, você não pode atribuir a ele um tipo genérico semelhante com base em
uma instância de tipo diferente e incompatível. Se isso parece confuso, este exemplo deve ajudar
a esclarecer as coisas:

Sg1 := TSimpleGeneric<Integer>.Create; // Erro


// E2010 Tipos incompatíveis:
// 'TSimpleGeneric<System.string>' // e
'TSimpleGeneric<System.Integer>'

Como veremos na seção “Regras de compatibilidade de tipos genéricos”, neste caso peculiar, a
regra de compatibilidade de tipos é por estrutura e não por nome de tipo. Você não pode atribuir
um tipo diferente e incompatível a um tipo genérico depois de declarado.

Genéricos em Object Pascal


No exemplo anterior vimos como você pode definir e usar uma classe genérica em Object
Pascal. Decidi apresentar esse recurso com um exemplo antes de me aprofundar nos aspectos
técnicos, que são bastante complexos e muito importantes ao mesmo tempo. Depois de abordar
os genéricos do ponto de vista da linguagem, voltaremos a mais exemplos, incluindo o uso e a
definição de classes contêineres genéricas, um dos principais usos dessa técnica na linguagem.

Vimos que quando você define uma classe você pode adicionar um “parâmetro” extra entre
colchetes para ocupar o lugar de um tipo a ser fornecido posteriormente:

tipo
TMyClass<T> = classe
fim;

O tipo genérico pode ser usado como o tipo de um campo (como fiz no exemplo anterior), como o
tipo de uma propriedade, como o tipo de um parâmetro ou valor de retorno de uma função e
muito mais. Observe que não é obrigatório o uso do tipo para um campo local (ou array), pois
há casos em que o tipo genérico é usado apenas como resultado, um parâmetro, ou não é usado
na declaração da classe, mas apenas na definição de alguns de seus métodos.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 389

Essa forma de declaração de tipo estendida ou genérica não está disponível apenas para classes, mas também
para registros (que, como abordei no Capítulo 5, também podem ter métodos, propriedades e
operadores sobrecarregados). Uma classe genérica também pode ter vários tipos parametrizados, como no caso
a seguir, em que você pode especificar um parâmetro de entrada e um valor de retorno de um tipo diferente para
um método:

tipo
TPWGeneric<TInput, TReturn> = classe
público
função AnyFunction(Valor: TInput): TReturn;
fim;

A implementação de genéricos em Object Pascal, como em outras linguagens estáticas, não é baseada em
suporte de tempo de execução. Ele é tratado pelo compilador e pelo vinculador, não deixando quase nada para o
mecanismo de tempo de execução. Ao contrário das chamadas de funções virtuais, que são vinculadas em
tempo de execução, os métodos de classe genéricos são gerados uma vez para cada tipo genérico que você
instancia e são gerados em tempo de compilação! Veremos as possíveis desvantagens desta abordagem,
mas pelo lado positivo ela implica que as classes genéricas são tão eficientes quanto as classes simples.
classes, ou ainda mais eficiente à medida que a necessidade de verificações de tempo de execução é
reduzida. Antes de examinarmos alguns dos aspectos internos, deixe-me focar em algumas regras muito
significativas que quebram as regras tradicionais de compatibilidade de tipos de linguagem Pascal.

Regras de compatibilidade de tipos genéricos


No Pascal tradicional e no Object Pascal, as regras de compatibilidade de tipos principais são baseadas em
equivalência de nome de tipo. Em outras palavras, duas variáveis são compatíveis com o tipo somente se o nome
do tipo for o mesmo, independentemente da estrutura de dados real à qual se referem.

Este é um exemplo clássico de incompatibilidade de tipo com matrizes estáticas (parte do exemplo TypeCom-
pRules ):

tipo
TArrayOf10 = array[1..10] de Inteiro;

procedimento TForm30.Button1Click(Remetente: TObject);


era
Matriz1: TArrayOf10;
Matriz2: TArrayOf10
Array3, Array4: array[1..10] de Inteiro;
começar
Matriz1 := Matriz2;
Matriz2 := Matriz3; // Erro
// E2010 Tipos incompatíveis: 'TArrayOf10' e 'Variedade'

Matriz3 := Matriz4;
Matriz4 := Matriz1; // Erro
// E2010 Tipos incompatíveis: 'Array' e 'TArrayOf10'
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

390 - 14: genéricos

Como você pode ver no código acima, todos os quatro arrays são estruturalmente idênticos. No entanto, o
O compilador permitirá que você atribua apenas aqueles que são compatíveis com o tipo, seja porque
seu tipo tem o mesmo nome explícito (como TArrayOf10) ou porque eles têm o mesmo nome de
tipo implícito (gerado pelo compilador) que os arrays declarados em uma única instrução.

Esta regra de compatibilidade de tipos tem exceções muito limitadas, como aquelas relacionadas a
classes derivadas. Outra exceção à regra, e significativa, é a compatibilidade de tipos para tipos
genéricos, que provavelmente também é usada internamente pelo compilador para determinar
quando gerar um novo tipo a partir do genérico, com todos os seus métodos.

A nova regra afirma que os tipos genéricos são compatíveis quando compartilham o mesmo nome genérico.
definição de classe e tipo de instância, independentemente do nome do tipo associado a esta definição.
Em outras palavras, o nome completo da instância do tipo genérico é uma combinação do tipo genérico
e do tipo de instância.

No exemplo a seguir, as quatro variáveis são todas compatíveis com o tipo:

tipo
TGenericArray<T> = classe
AnArray: array[1..10] de T;
fim;
TIntGenericArray = TGenericArray<Integer>;

procedimento TForm30.Button2Click(Remetente: TObject);


era
Matriz1: TIntGenericArray;
Array2: TIntGenericArray;
Array3, Array4: TGenericArray<Integer>;
começar
Array1 := TIntGenericArray.Create;
Matriz2 := Matriz1;
Matriz3 := Matriz2;
Matriz4 := Matriz3;
Matriz1 := Matriz4;
fim;

Métodos genéricos para classes padrão

Embora o uso de tipos genéricos para definir classes seja provavelmente o cenário mais comum, os
tipos genéricos também podem ser usados em classes não genéricas. Em outras palavras, uma classe
regular pode ter um método genérico. Nesse caso, você não especifica apenas um tipo para o espaço
reservado genérico ao criar uma instância da classe, mas também ao invocar o método. Aqui está um
exemplo de classe com um método genérico do GenericMethod
exemplo:

tipo
TGenericFunction = classe
público
função WithParam<T>(T1:T): string;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 391

fim;

note Quando escrevi este código pela primeira vez, provavelmente com uma reminiscência dos meus dias de C++, escrevi o parâmetro como
(t: T). Escusado será dizer que em uma linguagem que não diferencia maiúsculas de minúsculas como Object Pascal, esta não é
uma boa ideia. O compilador irá realmente deixá-lo ir, mas emitirá erros toda vez que você se referir ao tipo genérico T.

Não há muito que você possa fazer dentro de um método de classe semelhante (pelo menos a menos que você
use restrições, abordadas posteriormente neste capítulo), então escrevi alguns códigos usando funções especiais
de tipo genérico (novamente abordadas posteriormente) e uma função especial para converta o tipo em uma
string, que não é relevante discutir aqui:

função TGenericFunction.WithParam<T>(T1:T): string;


começar
Resultado: = GetTypeName(TypeInfo(T));
fim;

Como você pode ver, esse método nem usa o valor real passado como parâmetro, mas apenas captura algumas
informações de tipo. Mais uma vez, não saber o tipo de T1 torna
bastante complexo para usá-lo no código.

Você pode chamar várias versões desta “função genérica global” da seguinte forma:

era
GF: TGenericFunction;
começar
GF := TGenericFunction.Create;
tentar
Mostrar(GF.WithParam<string>()); 'Foo'
Mostrar(GF.WithParam<Integer>(122));
Mostrar(GF.WithParam()); 'Olá'
Mostrar(GF.WithParam(122));
Mostrar(GF.WithParam(Botão1));
Mostrar(GF.WithParam<TObject>(Button1));
finalmente
GF.Livre;
fim;

Todas as chamadas acima estão corretas, pois o tipo paramétrico pode estar implícito nessas chamadas.
Observe que o tipo genérico é exibido (conforme especificado ou inferido) e não o tipo real do parâmetro, o que
explica esta saída:

corda
Inteiro
corda
CurtoInt
Botão T
TObject

Se você chamar o método sem indicar o tipo entre colchetes angulares, o tipo real será inferido do tipo do
parâmetro. Se você chamar o método com um tipo e um

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

392 - 14: genéricos

parâmetro, o tipo do parâmetro deve corresponder à declaração de tipo genérico. Portanto, as três linhas
abaixo não serão compiladas:

Show(GF.WithParam<Integer>()); 'Foo'
Mostrar(GF.WithParam<string>(122));
Mostrar(GF.WithParam<TButton>(Self));

Instanciação de tipo genérico


Observe que esta é uma seção bastante avançada com foco em alguns aspectos internos dos
genéricos e sua otimização potencial. Bom para uma segunda leitura, não se for
a primeira vez que você está pesquisando genéricos.

Com exceção de algumas otimizações, toda vez que você instancia um tipo genérico, seja em um
método ou em uma classe, um novo tipo é gerado pelo compilador. Este novo tipo não compartilha
código com diferentes instâncias do mesmo tipo genérico (ou diferentes versões do mesmo método).

Vejamos um exemplo (que faz parte do exemplo GenericCodeGen ). O programa possui uma classe
genérica definida como:

tipo
TSampleClass<T> = classe
privado
Dados F: T;
público
procedimento Um;
função LeituraT: T;
procedimento SetT(Valor: T);
fim;

Os três métodos são implementados da seguinte forma (observe que o método One é absolutamente
independente do tipo genérico):

procedimento TSampleClass<T>.One;
começar
'UmT'
Form30.Mostrar(fim; );

função TSampleClass<T>.ReadT: T;
começar
Resultado := DadosF;
fim;

procedimento TSampleClass<T>.SetT(Valor: T);


começar
FDados := Valor;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 393

Agora, o programa principal usa o tipo genérico principalmente para descobrir o endereço na
memória de seus métodos, uma vez que uma instância é gerada pelo compilador. Este é o código

procedimento TForm30.Button1Click(Remetente: TObject);


era
T1: TSampleClass<Integer>;
T2: TSampleClass<string>;
começar
T1 := TSampleClass<Integer>.Create;
T1.SetT(10);
T1.Um;

T2 := TSampleClass<string>.Create;
T2.SetT(); 'Olá'
T2.Um;
' +
'T1.SetT:
Show(IntToHex(PInteger(@TSampleClass<Integer>.SetT)^,
' + 8));
'T2.SetT:
Show(IntToHex(PInteger(@TSampleClass<string>.SetT)^, 8));
' +
'T1.Um:
Show(IntToHex(PInteger(@TSampleClass<Integer>.One)^,
' + 8));
'T2.Um:
Show(IntToHex(PInteger(@TSampleClass<string>.One)^, 8));
fim;

O resultado é mais ou menos assim (os valores reais variam):

T1.ConjuntoT: C3045089
T2.ConjuntoT: 51EC8B55
T1.Um: 4657F0BA
T2.Um: 46581CBA

Como previ, não apenas o método SetT obtém uma versão diferente na memória gerada pelo
compilador para cada tipo de dados usado, mas até mesmo o método One obtém, apesar de
serem todos idênticos.
Além disso, se você declarar novamente um tipo genérico idêntico, obterá um novo conjunto de
funções de implementação. Da mesma forma, a mesma instância de um tipo genérico usada
em unidades diferentes força o compilador a gerar o mesmo código repetidamente, possivelmente
causando um inchaço significativo do código. Por esta razão se você tiver uma classe genérica
com muitos métodos que não dependem do tipo genérico, é recomendado definir uma classe base
não genérica com esses métodos comuns e uma classe genérica herdada com os métodos
genéricos: desta forma o os métodos da classe base são compilados e incluídos no executável apenas uma

note Atualmente há trabalho de compilador, vinculador e RTL de baixo nível sendo feito para reduzir o aumento de tamanho
causados por genéricos em cenários como os descritos nesta seção. Veja, por exemplo, as
considerações em http://delphisorcery.blogspot.it/2014/10/new-language-feature-in-xe7.html.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

394 - 14: genéricos

Funções de tipo genérico


O maior problema com as definições de tipos genéricos que vimos até agora é que há muito pouco
que se possa fazer com elementos do tipo de classe genérico. Existem duas técnicas que
você pode usar para superar essa limitação. A primeira é fazer uso das poucas funções
especiais da biblioteca de tempo de execução que suportam especificamente tipos genéricos; a
segunda (e muito mais poderosa) é definir classes genéricas com restrições nos tipos que
você pode usar.
Vou me concentrar na primeira técnica nesta seção e nas restrições na próxima seção. Como
mencionei, existem algumas funções RTL que funcionam no tipo paramétrico (T) de uma definição
de tipo genérico:

· Default (T) é na verdade uma nova função introduzida junto com os genéricos que retorna o valor
vazio ou “valor zero” ou nulo para o tipo atual; pode ser zero, uma string vazia, nil e assim
por diante; a memória inicializada com zero tem o mesmo valor de uma variável global do
mesmo tipo (diferentemente das variáveis locais, na verdade, as globais são inicializadas em
“zero” pelo compilador)
· TypeInfo (T) retorna o ponteiro para as informações de tempo de execução da versão atual
do tipo genérico; você encontrará muito mais informações sobre informações de tipo em
Capítulo 16
· SizeOf (T) retorna o número de bytes do tipo (que no caso de uma referência-
tipo, como uma string, ou um objeto, seria o tamanho da referência, ou seja, 4 bytes para um
compilador de 32 bits e 8 bytes para um compilador de 64 bits)
· IsManagedType(T) indica se o tipo é gerenciado em memória, como acontece com
strings e matrizes dinâmicas
· HasWeakRef(T) está vinculado a compiladores habilitados para ARC e indica se o tipo de destino
possui referências fracas; que requer suporte específico de gerenciamento de memória
· GetTypeKind(T) é um atalho para acessar o tipo de tipo a partir das informações de tipo; que é
uma definição de tipo de nível ligeiramente superior ao retornado por TypeInfo

note Todos esses métodos retornam constantes avaliadas pelo compilador em vez de chamar funções reais em tempo
de execução. A importância disso não está no fato de essas operações serem muito rápidas, mas sim no fato
de possibilitar ao compilador e ao vinculador otimizar o código gerado, removendo ramificações não utilizadas. Se
você tem um case ou uma instrução if baseada no valor de retorno de uma dessas funções, o compilador pode
descobrir que, apenas para um determinado tipo, uma das ramificações será executada, removendo o código
inútil. Quando o mesmo método genérico é compilado para um tipo diferente, ele pode acabar usando uma
ramificação diferente, mas novamente o compilador pode descobrir antecipadamente e otimizar o tamanho do método.

O exemplo GenericTypeFunc possui uma classe genérica que mostra as três funções de tipo genérico em
ação:

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 395

TSampleClass<T> = classe
privado
Dados F: T;
público
procedimento Zero;
função GetDataSize: Inteiro;
função GetDataName: string;
fim;

função TSampleClass<T>.GetDataSize: Inteiro;


começar
Resultado:= SizeOf(T);
fim;

função TSampleClass<T>.GetDataName: string;


começar
Resultado: = GetTypeName(TypeInfo(T));
fim;

procedimento TSampleClass<T>.Zero;
começar
FData := Padrão(T);
fim;

No método GetDataName utilizei a função GetTypeName (do System.TypInfo


unidade), em vez de acessar diretamente a estrutura de dados, porque executa a conversão
adequada do valor da string codificada que contém o nome do tipo.
Dada a declaração acima, você pode compilar o seguinte código de teste que se repete
três vezes em três instâncias de tipo genérico diferentes. Omiti o código repetido e
simplesmente mostro as instruções usadas para acessar o campo de dados , pois elas
mudam dependendo do tipo real:
era
T1: TSampleClass<Integer>;
T2: TSampleClass<string>;
T3: TSampleClass<Duplo>;
começar
T1 := TSampleClass<Integer>.Create;
T1.Zero;
'TSampleClass<Inteiro>' );
'Dados: ' + IntToStr(T1.FData));
'
'Tipo: ' + T1.GetDataName);
'Tamanho: + IntToStr(T1.GetDataSize));
Mostrar(Mostrar(Mostrar(Mostrar(

T2 := TSampleClass<string>.Create;
'Dados: '
Mostrar(+T2.FDados);

T3 := TSampleClass<Duplo>.Create;
'
'Dados:
Mostrar(+ FloatToStr(T3.FData));

A execução deste código (do exemplo GenericTypeFunc ) produz esta saída:


TSampleClass<Inteiro>

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

396 - 14: genéricos

Dados: 0
Tipo: Inteiro
Tamanho: 4
TSampleClass<string>
Dados:
Tipo: string
Tamanho: 4
TSampleClass<Duplo>
Dados: 0
Tipo: Duplo
Tamanho: 8

Observe que você também pode usar funções de tipo genérico em tipos específicos, fora do contexto
de classes genéricas. Por exemplo, você pode escrever:

era
Eu: Inteiro;
s: corda;
começar
I := Padrão (Inteiro); '
Mostrar( 'Inteiro padrão : + IntToStr(I));

s := Padrão(string); '
Mostrar( 'Sequência padrão : +s);
'
Mostrar( 'String TypeInfo : + GetTypeNome(TypeInfo(string));

Esta é a saída trivial:

Inteiro padrão: 0
Sequência padrão:
String TypeInfo: string

note Você não pode aplicar a chamada TypeInfo a uma variável, como TypeInfo(s) no código acima, mas apenas a um
tipo de dados.

Construtores de classes para classes genéricas

Um caso muito interessante surge quando você define um construtor de classe para uma classe
genérica. Na verdade, um desses construtores é gerado pelo compilador e chamado para cada classe genérica
instância, isto é, para cada tipo real definido usando o modelo genérico. Isso é bastante interessante,
porque seria muito complexo executar código de inicialização para cada instância real da classe
genérica que você criará em seu programa sem um construtor de classe.

Por exemplo, considere uma classe genérica com alguns dados de classe. Você obterá uma instância
desses dados de classe para cada instância de classe genérica. Se você precisar atribuir um valor
inicial aos dados desta classe, não poderá usar o código de inicialização da unidade, pois na unidade
que define a classe genérica você não sabe quais classes reais serão necessárias.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 397

A seguir está um exemplo básico de uma classe genérica com um construtor de classe usado para
inicialize o campo da classe DataSize , obtido do exemplo GenericClassCtor :

tipo
TGenericWithClassCtor<T> = classe
privado
Dados F: T;
procedimento SetData (valor const: T);
público
construtor de classe Criar;
Dados de propriedade: T ler FData escrever SetData;
a aula era
DataSize: Inteiro;
fim;

Este é o código do construtor de classe genérico, que usa uma lista de strings interna (veja o código-
fonte completo para detalhes de implementação) para controlar quais construtores de classe são
realmente chamados:

construtor de classe TGenericWithClassCtor<T>.Create;


começar
DataSize := SizeOf(T);
ListSequence.Add(ClassName);
fim;

O programa de demonstração cria e usa algumas instâncias da classe genérica e também declara o tipo
de dados para uma terceira, que é removida pelo vinculador:

era
GenInt: TGenericWithClassCtor<SmallInt>;
GenStr: TGenericWithClassCtor<string>;
tipo
TGenDouble = TGenericWithClassCtor<Duplo>;

Se você pedir ao programa para mostrar o conteúdo da lista de strings ListSequence , você verá apenas
os tipos que foram realmente inicializados:

TGenericWithClassCtor<System.SmallInt>
TGenericWithClassCtor<System.string>

Entretanto, se você criar instâncias genéricas baseadas no mesmo tipo de dados, em unidades
diferentes, o vinculador poderá não funcionar conforme o esperado e você terá vários construtores de
classes genéricas para o mesmo tipo.

observação Não é fácil resolver esse problema. Para evitar uma inicialização repetida, você pode querer
verificar se o construtor da classe já foi executado. Em geral, porém, esse problema é parte de uma
limitação mais abrangente das classes genéricas e da incapacidade dos linkers de otimizá-las.

Adicionei um procedimento chamado Useless na segunda unidade deste exemplo que, quando
descomentado, irá destacar o problema, com a seguinte sequência de inicialização:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

398 - 14: genéricos

TGenericWithClassCtor<System.string>
TGenericWithClassCtor<System.SmallInt>
TGenericWithClassCtor<System.string>

Restrições genéricas

Como vimos, há muito pouco que você possa fazer nos métodos da sua classe genérica em relação ao
valor do tipo genérico. Você pode distribuí-lo (ou seja, atribuí-lo) e executar as operações limitadas
permitidas pelas funções de tipo genérico que abordei acima.

Para poder executar algumas operações reais do tipo genérico de classe, geralmente é necessário colocar
uma restrição nela. Por exemplo, se você limitar o tipo genérico a uma classe, o compilador permitirá que
você chame todos os métodos TObject nele. Você também pode restringir ainda mais a classe para que
faça parte de uma determinada hierarquia ou para implementar uma interface específica, tornando
possível chamar a classe ou o método de interface em uma instância do tipo genérico.

Restrições de classe

A restrição mais simples que você pode adotar é uma restrição de classe. Para usá-lo, você pode declarar
um tipo genérico como este:

tipo
TSampleClass<T:classe> =classe

Ao especificar uma restrição de classe, você indica que só pode usar tipos de objetos como tipos
genéricos. Com a seguinte declaração (retirada do exemplo ClassConstraint ):

tipo
TSampleClass<T:classe> =classe
privado
Dados F: T;
público
procedimento Um;
função LeituraT: T;
procedimento SetT(T1:T);
fim;

você pode criar as duas primeiras instâncias, mas não a terceira:

Amostra1: TSampleClass<TButton>;
Amostra2: TSampleClass<TStrings>;
Amostra3: TSampleClass<Integer>; // Erro

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 399

O erro do compilador causado por esta última declaração seria:

E2511 O parâmetro de tipo 'T' deve ser um tipo de classe

Qual é a vantagem de indicar essa restrição? Nos métodos de classe genéricos agora você
pode chamar qualquer método TObject , inclusive os virtuais! Este é o método One da classe
genérica TSampleClass :

procedimento TSampleClass<T>.One;
começar
se atribuído (FData) então
começar
'
'Nome da classe: + FData.ClassName);
'
'Tamanho: + IntToStr(FData.InstanceSize));
'
'Para sequenciar: + FData.ToString);
Form30.Show(Form30.Show(Form30.Show(end;
fim;

note Dois comentários aqui. A primeira é que InstanceSize retorna o tamanho real do objeto, ao contrário da
função genérica SizeOf que usamos anteriormente, que retorna o tamanho do tipo de referência.
Segundo, observe o uso do método ToString da classe TObject .

Você pode brincar com o programa para ver seu efeito real, pois ele define e usa algumas
instâncias do tipo genérico, como no seguinte trecho de código:

era
Amostra1: TSampleClass<TButton>;
começar
Amostra1 := TSampleClass<TButton>.Create;
tentar
Sample1.SetT(Remetente como TButton);
Amostra1.Um;
finalmente
Amostra1.Grátis;
fim;

Observe que ao declarar uma classe com um método ToString customizado , esta versão será
chamada quando o objeto de dados for do tipo específico, independentemente do tipo real
fornecido ao tipo genérico. Em outras palavras, se você tiver um descendente de TButton como:

tipo
TMyButton = classe(TButton)
público
função ToString: string; sobrepor;
fim;

Você pode passar este objeto como valor de um TSampleClass<TButton> ou definir uma
instância específica do tipo genérico, e em ambos os casos chamar One acaba executando a
versão específica de ToString:

era

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

400 - 14: genéricos

Amostra1: TSampleClass<TButton>;
Amostra2: TSampleClass<TMyButton>;
MB: TMyButton;
começar
...
Amostra1.SetT(Mb);
Amostra1.Um;
Amostra2.SetT(Mb);
Amostra2.Um;

Da mesma forma que uma restrição de classe, você pode ter uma restrição de registro, declarada como:

tipo
TSampleRec<T: registro> = classe

Entretanto, há muito pouco que registros diferentes tenham em comum (não há ancestral comum), portanto esta
declaração é um tanto limitada.

Restrições de classe específicas


Se sua classe genérica precisa trabalhar com um subconjunto específico de classes (uma hierarquia específica),
você pode querer recorrer à especificação de uma restrição baseada em uma determinada classe base.
Por exemplo, se você declarar:

tipo
TCompClass<T: TComponent> = classe

instâncias desta classe genérica podem ser aplicadas apenas a classes de componentes, ou seja, qualquer classe
descendente de TComponent . Isso permite que você tenha um tipo genérico muito específico (sim, parece
estranho, mas é o que realmente é) e o compilador permitirá que você use todos os métodos da classe
TComponent enquanto trabalha no tipo genérico.

Se isso parece extremamente poderoso, pense duas vezes. Se você considerar o que pode alcançar com herança
e regras de compatibilidade de tipos, poderá resolver o mesmo problema usando técnicas tradicionais orientadas
a objetos, em vez de usar classes genéricas. Não estou dizendo que uma restrição de classe específica nunca seja
útil, mas certamente não é tão poderosa quanto uma restrição de classe de nível superior ou (algo que considero
muito interessante) uma restrição baseada em interface.

Restrições de interface

Em vez de restringir uma classe genérica a uma determinada classe, geralmente é mais flexível aceitar, como
parâmetro de tipo, apenas classes que implementem uma determinada interface. Isto torna possível chamar a
interface em instâncias do tipo genérico. Esse uso de restrições de interface para genéricos também é muito comum
na linguagem C#. Deixe-me começar mostrando

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 401

você um exemplo (do exemplo IntfConstraint ). Primeiro, precisamos declarar uma interface:

tipo
IGetValue = interface
'{60700EC4-2CDA-4CD1-A1A2-07973D9D2444}' ]
[função GetValue: Inteiro; procedimento
SetValue(Valor: Inteiro); valor da propriedade: leitura
inteira GetValue gravação SetValue; fim;

A seguir, podemos definir uma classe que o implementa:

tipo
TGetValue = classe(TNoRefCountObject, IGetValue) privado

FValor: Inteiro; construtor


público
Criar (Valor: Inteiro = 0); função GetValue: Inteiro;
procedimento SetValue(Valor: Inteiro); fim;

As coisas começam a ficar interessantes na definição de uma classe genérica limitada aos tipos que
implementam a interface dada:

tipo
TInftClass<T: IGetValue> = classe privada

FVal1, FVal2: T; // Ou IGetValue

procedimento público Conjunto1(Val:


T); procedimento Conjunto2(Val: T);
função GetMin: Inteiro; função
GetAverage: Inteiro; procedimento
AumentaByTen; fim;

Observe que, no código dos métodos genéricos desta classe, podemos, por exemplo, escrever:

função TInftClass<T>.GetMin: Inteiro; resultado inicial: = Min

(FVal1.GetValue, FVal2.GetValue); fim;

procedimento TInftClass<T>.IncreaseByTen; começar

FVal1.SetValue(FVal1.GetValue + 10); FVal2.Valor :=


FVal2.Valor + 10; fim;

Com todas essas definições, podemos agora usar a classe genérica da seguinte forma:

procedimento TFormIntfConstraint.BtnValueClick(Sender: TObject);


era

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

402 - 14: genéricos

IClass: TInftClass<TGetValue>;
começar
IClass := TInftClass<TGetValue>.Create;
tentar
IClass.Set1(TGetValue.Create(5));
IClass.Set2(TGetValue.Create(25));
'
Mostrar(+IntToStr(IClass.GetAverage));
'Média:
IClass.IncreaseByTen;
'
'Mínimo:
Mostrar(+IntToStr(IClass.GetMin));
finalmente
IClass.FVal1.Free;
IClass.FVal2.Free;
IClass.Free;
fim;
fim;

Para mostrar a flexibilidade desta classe genérica, criei outra implementação totalmente diferente para a
interface:

tipo
TButtonValue = classe(TButton, IGetValue)
público
função GetValue: Inteiro;
procedimento SetValue(Valor: Inteiro);
função de classe MakeTButtonValue (Proprietário: TComponent;
Pai: TWinControl): TButtonValue;
fim;

função TButtonValue.GetValue: Inteiro;


começar
Resultado := Esquerda; // usa a propriedade da classe base
fim;

procedimento TButtonValue.SetValue(Valor: Inteiro);


começar
Esquerda:= Valor; // usa a propriedade da classe base
fim;

A função de classe (não mostrada aqui) cria um botão dentro de um controle Parent em uma posição
aleatória e é usada no seguinte código de exemplo:

procedimento TFormIntfConstraint.BtnValueButtonClick(Sender: TObject);


era
IClass: TInftClass<TButtonValue>;
começar
IClass := TInftClass<TButtonValue>.Create;
tentar
IClass.Set1(TButtonValue.MakeTButtonValue(Self, ScrollBox1));
IClass.Set2(TButtonValue.MakeTButtonValue(Self, ScrollBox1));
'
Mostrar(+IntToStr(IClass.GetAverage));
'Média: '
'Mínimo:
Mostrar(+IntToStr(IClass.GetMin));
IClass.IncreaseByTen; '
'Novo Média:
Mostrar(+IntToStr(IClass.GetAverage));
finalmente
IClass.Free;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 403

fim;
fim;

Referências de interface vs. restrições de interface genérica

No último exemplo definimos uma classe genérica que funciona com qualquer objeto que implemente
uma determinada interface. Eu poderia ter obtido um efeito semelhante criando uma classe padrão (não
genérica) baseada em referências de interface. Na verdade, eu poderia ter definido uma classe como
(novamente parte do exemplo IntfConstraint ):

tipo
TplainInftClass = classe
privado
FVal1, FVal2: IGetValue;
público
procedimento Set1(Val: IGetValue);
procedimento Set2(Val: IGetValue);
função GetMin: Inteiro;
função GetAverage: Inteiro;
procedimento AumentaByTen;
fim;

O que há de diferente entre essas duas abordagens? Uma primeira diferença é que, na classe acima,
você pode passar dois objetos de tipos diferentes para os métodos setter, desde que suas classes
implementem a interface dada, enquanto na versão genérica você pode passar apenas objetos do tipo
dado ou derivado de esse tipo (para qualquer instância da classe genérica). Portanto a versão genérica
é mais conservadora e rigorosa em termos de verificação de tipo.

Na minha opinião, a principal diferença é que, usar a versão baseada em interface, significa ter o
mecanismo de contagem de referências do Object Pascal em ação, enquanto, usando a versão genérica,
a classe está lidando com objetos simples de um determinado tipo e contagem de referências. não está
envolvido.

Além disso, a versão genérica pode ter múltiplas restrições, como uma restrição de construtor e permite
usar várias funções genéricas (como solicitar o tipo real do tipo genérico), algo que você não pode fazer ao
usar uma interface. (Quando você está trabalhando com uma interface, na verdade, você não tem como
fazer referência aos métodos básicos do TObject ).

Em outras palavras, usar uma classe genérica com uma restrição de interface possibilita obter os
benefícios das interfaces sem seus incômodos. Ainda assim, vale a pena notar que na maioria dos casos
as duas abordagens seriam equivalentes, e noutros a solução baseada em interface seria mais flexível.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

404 - 14: genéricos

Restrição do construtor padrão

Existe outra possível restrição de tipo genérico, chamada construtor padrão ou construtor sem
parâmetros. Se você precisar invocar o construtor padrão para criar um novo objeto do tipo
genérico, por exemplo, para preencher uma lista, poderá usar esta restrição. Em teoria, e de
acordo com a documentação, o compilador deveria permitir que você o usasse apenas para
os tipos com um construtor padrão. Na prática, se não existir um construtor padrão, o
compilador irá deixá-lo ir e chamar o construtor padrão de TObject.
Uma classe genérica com uma restrição de construtor pode ser escrita da seguinte
forma (esta é extraída pelo exemplo IntfConstraint ):

tipo
TConstrClass<T: classe, construtor> = classe
privado
ValorF: T;
público
construtor Criar;
função Obter: T;
fim;

note Você também pode especificar a restrição do construtor sem a restrição da classe, pois possui um construtor
implica que o tipo é uma classe. Listar os dois torna o código mais legível.

Dada esta declaração, você pode usar o construtor para criar um objeto interno genérico,
sem saber seu tipo real antecipadamente, e escrever:

construtor TConstrClass<T>.Create;
começar
FVal := T.Criar;
fim;

Como podemos usar esta classe genérica e quais são as regras reais? No próximo exemplo
defini duas classes, uma com um construtor padrão sem parâmetros, a segunda com
um único construtor com um parâmetro:

tipo
TSimpleConst = classe
público
FValor: Inteiro;
construtor Criar; fim; // Definir Valor para 10

TParamConst = classe
público
FValor: Inteiro;
construtor Criar (I: Inteiro); fim; // Definir Valor para EU

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 405

Como mencionei anteriormente, em teoria você só deveria conseguir usar a primeira classe, enquanto na prática
você pode usar ambas:

era
ConstruirObj: TConstrClass<TSimpleCost>;
ParamCostObj: TConstrClass<TParamCost>;
começar
ConstructObj := TConstrClass<TSimpleCost>.Create;
'
'Valor
Mostrar(+ 1:
IntToStr(ConstructObj.Get.FValue));

ParamCostObj := TConstrClass<TParamCost>.Create;
'
'Valor
Mostrar(+ 2:
IntToStr(ParamCostObj.Get.FValue));

A saída deste código é:

Valor 1: 10
Valor 2: 0

Na verdade, o segundo objeto nunca é inicializado. Se você depurar o rastreamento do aplicativo no


código, você verá uma chamada para TObject.Create (que considero errado). Observe que se você tentar
ligando diretamente:

com TParamConst.Create faça

o compilador irá (corretamente) gerar o erro:

[Erro DCC] E2035 Parâmetros reais insuficientes

note Mesmo que uma chamada direta para TParamConst.Create falhe em tempo de compilação (conforme explicado aqui), uma chamada semelhante
usar uma referência de classe, ou qualquer outra forma de indireção, terá sucesso; o que provavelmente explica o
comportamento do efeito da restrição do construtor.

Resumo das restrições e combinação delas


Como existem tantas restrições diferentes que você pode colocar em um tipo genérico, deixe-me fornecer
um breve resumo aqui, em termos de código:

tipo
TSampleClass<T:classe> =classe
TSampleRec<T: registro> = classe
TCompClass<T: TButton> = classe
TInftClass<T: IGetValue> = classe
TConstrClass<T: construtor> = classe

O que você pode não perceber imediatamente depois de observar as restrições (e levei algum tempo para me
acostumar) é que você pode combiná-las. Por exemplo, você pode definir uma classe genérica limitada a uma
subhierarquia e exigindo também uma determinada interface, como em:

tipo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

406 - 14: genéricos

TInftComp<T: TComponent, IGetValue> = classe

Nem todas as combinações fazem sentido: por exemplo, você não pode especificar uma classe e um registro
ao mesmo tempo, enquanto usar uma restrição de classe combinada com uma restrição de classe específica
seria redundante. Finalmente, observe que não há nada como uma restrição de método, algo que pode ser
alcançado com uma restrição de interface de método único (embora muito mais complexa de expressar).

Contêineres genéricos predefinidos

Desde os primórdios dos modelos na linguagem C++, um dos usos mais óbvios das classes de modelos tem sido
a definição de contêineres de modelos, ou listas, até o ponto em que a linguagem C++ definiu uma Biblioteca de
Modelos Padrão (ou STL).

Quando você define uma lista de objetos, como o próprio TObjectList do Object Pascal, você tem uma lista que
pode potencialmente conter objetos de qualquer tipo. Usando herança ou composição, você pode definir
contêineres personalizados para um tipo específico, mas essa é uma abordagem tediosa (e potencialmente
propensa a erros).

Os compiladores Object Pascal vêm com um pequeno conjunto de classes de contêiner genéricas que você pode
encontrar na unidade Generics.Collections . As quatro classes principais de contêineres são todas implementadas
de maneira independente (não há herança entre essas classes), todas implementadas de maneira semelhante
(usando um array dinâmico) e todas mapeadas para a classe de contêiner não genérica correspondente da
unidade Contnrs mais antiga :

tipo
TList<T> = classe
TQueue<T> = classe
TStack<T> = classe
TDictionary<K, V> = classe
TObjectList<T: classe> = classe(TList<T>)
TObjectQueue<T: classe> = classe(TQueue<T>)
TObjectStack<T: classe> = classe(TStack<T>)
TObjectDictionary<K, V> = classe(TDictionary<K, V>)

A diferença lógica entre essas classes deveria ser bastante óbvia considerando seus nomes. Uma boa maneira
de testá-los é ver quantas alterações você precisa realizar no código existente que usa uma classe de contêiner
não genérica em comparação com o uso da versão genérica para diferentes tipos de dados.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 407

note O programa discutido a seguir, ListDemoMd2005, usa apenas alguns métodos, portanto não é um ótimo
teste para compatibilidade de interface entre listas genéricas e não genéricas, mas decidi usar um
programa existente em vez de fabricar um. Outra razão para mostrar esta demonstração é que você
também pode ter programas existentes que não usam classes de coleção genéricas e será incentivado a aprimorá-lo
aproveitando esse recurso de idioma.

Usando TList<T>
O programa, denominado ListDemoMd2005, possui uma unidade que define uma classe TDate , e o
formulário principal utilizado para se referir a uma TList de datas. Como ponto de partida, adicionei uma
cláusula de uso referente a Generics.Collections, depois alterei a declaração do campo do formulário principal
para:

privado
FListDate: TList<TDate>;

É claro que o manipulador de eventos OnCreate do formulário principal que cria a lista também precisava
ser atualizado, tornando-se:

procedimento TForm1.FormCreate(Remetente: TObject);


começar
FListDate := TList<TDate>.Create;
fim;

Agora podemos tentar compilar o resto do código como está. O programa tem um bug “procurado”,
tentando adicionar um objeto TButton à lista. O código correspondente usado para compilar e agora falha:

procedimento TForm1.ButtonWrongClick(Remetente: TObject);


começar
// Adicione um botão para a lista
FListDate.Add(Remetente); // Erro:
// E2010 Tipos incompatíveis: 'TDate' e 'TObject'
fim;

A nova lista de datas é mais robusta em termos de verificação de tipo do que a lista genérica original de
ponteiros. Tendo removido essa linha, o programa compila e funciona. Ainda assim, pode
ser melhorado.

Este é o código original usado para exibir todas as datas da lista em um controle ListBox:

era
Eu: Inteiro;
começar
ListBox1.Limpar;
para I := 0 para ListDate.Count - 1 faça
ListBox1.Items.Add((TObject(FListDate[I]) como TDate).Texto);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

408 - 14: genéricos

Observe a conversão do tipo, devido ao fato do programa estar utilizando uma lista de ponteiros
(TList), e não uma lista de objetos (TObjectList).

Podemos facilmente melhorar o programa escrevendo:

para I := 0 para FListDate.Count - 1 faça


ListBox1.Items.Add(FListDate[I].Text);

Outra melhoria neste trecho pode vir do uso de uma enumeração (algo
as listas genéricas predefinidas suportam totalmente) em vez de um loop for simples :

era
Data AD: DataTD;
começar
para ADate em FListDate faça
começar
ListBox1.Items.Add(ADate.Text);
fim;

Finalmente, o programa pode ser melhorado usando um TObjectList genérico que possui os objetos
TDate , mas isso é assunto para a próxima seção.

Como mencionei anteriormente, a classe genérica TList<T> possui um alto grau de compatibilidade. Isto
possui todos os métodos clássicos, como Add, Insert, Remove e IndexOf. As propriedades Capacidade e Contagem
também estão lá. Estranhamente, Items se torna Item, mas sendo a propriedade padrão (acessada usando colchetes
sem o nome da propriedade), você raramente se refere explicitamente a ela de qualquer maneira.

Classificando um TList<T>
O que é interessante entender é como funciona a classificação TList<T> (meu objetivo aqui é adicionar
suporte de classificação ao exemplo ListDemoMd2005 ). O método Sort é definido como:

procedimento Classificar; sobrecarga;


procedimento Sort(const AComparer: IComparer<T>); sobrecarga;

onde a interface IComparer<T> é declarada na unidade Generics.Defaults . Se você ligar


na primeira versão do programa, ele utilizará o comparador padrão, inicializado pelo construtor padrão de
TList<T>. No nosso caso, isso será inútil.

O que precisamos fazer, em vez disso, é definir uma implementação adequada do IComparer<T>
interface. Para compatibilidade de tipos, precisamos definir uma implementação que funcione na classe
TDate específica .

Existem diversas maneiras de fazer isso, incluindo o uso de métodos anônimos (abordados brevemente
na próxima seção, mas abordados na íntegra no próximo capítulo). Uma técnica interessante, também
porque me dá a oportunidade de mostrar vários padrões de uso de genéricos, é aproveitar o que chamo
de uma classe estrutural que faz parte da unidade Generics.Defaults e se chama TComparer.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 409

note que estou chamando essa classe de estrutural porque ela ajuda a definir a estrutura do código, sua arquitetura, mas não
acrescenta muito em termos de implementação real. Uma classe semelhante é frequentemente chamada de classe
de estrutura, embora isso geralmente implique um design mais complexo.

A classe é definida como uma implementação abstrata e genérica da interface:

tipo
TComparer<T> = classe(TInterfacedObject, IComparer<T>)
público
função de classe Padrão: IComparer<T>;
função de classe Construir(
Comparação const: TComparison<T>): IComparer<T>;
função Comparar(
const Esquerda, Direita: T): Inteiro; virtual; abstrato;
fim;

O que precisamos fazer é instanciar essa classe genérica para o tipo de dados específico (TDate, no
exemplo) e também herdar uma classe concreta que implemente o método Compare para o tipo específico.
As duas operações podem ser feitas ao mesmo tempo, usando uma linguagem de codificação que pode
demorar um pouco para ser digerida:

tipo
TDateComparer = class(TComparer<TDate>)
função Comparar(
const Esquerda, Direita: TDate): Inteiro; sobrepor;
fim;

Se você acha que esse código parece muito incomum, você não está sozinho. A nova classe herda de uma
instância específica da classe genérica, algo que você poderia expressar em duas etapas separadas
como:

tipo
TAnyDateComparer = TComparer<TDate>;
TMyDateComparer = classe(TAnyDateComparer)
função Comparar(
const Esquerda, Direita: TDate): Inteiro; sobrepor;
fim;

note Ter as duas declarações separadas pode ajudar a reduzir o código gerado onde você está reutilizando o
tipo base TAnyDateComparer na mesma unidade.

Você pode encontrar a implementação real da função Compare no código-fonte, pois esse não é o ponto principal
que quero enfatizar aqui. Tenha em mente, porém, que mesmo se você ordenar a lista, seu método IndexOf
não tirará vantagem disso (ao contrário do método TStringList
aula).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

410 - 14: genéricos

Classificando com um método anônimo


O código de classificação apresentado na seção anterior parece bastante complicado e realmente
é. Seria muito mais fácil e limpo passar a função de classificação diretamente para o método Sort . No passado, isso
geralmente era conseguido passando um ponteiro de função. No objeto
Pascal isso pode ser feito passando um método anônimo (uma espécie de ponteiro de método, com vários
recursos extras, abordado em detalhes no próximo capítulo).

note que sugiro que você dê uma olhada nesta seção mesmo que não saiba muito sobre métodos anônimos, e
em seguida, leia-o novamente depois de passar pelo próximo capítulo que aborda detalhadamente os métodos anônimos.

O parâmetro IComparer<T> do método Sort da classe TList<T> , na verdade, pode ser usado chamando o método
Construct de TComparer<T>, passando um método anônimo
como um parâmetro definido como:

tipo
TComparison<T> = referência à função(
const Esquerda, Direita: T): Inteiro;

Na prática você pode escrever uma função compatível com o tipo e passá-la como parâmetro:

função DoCompare (const Esquerda, Direita: TDate): Inteiro;


era
LDate, RDate: TDateTime;
começar
LDate := EncodeDate(Esquerda.Ano, Esquerda.Mês, Esquerda.Dia);
RDate := EncodeDate(Right.Year, Right.Month, Right.Day);
se LDate = RDate então
Resultado:= 0
senão se LDate < RDate então
Resultado:= -1
outro
Resultado:= 1;
fim;

procedimento TForm1.ButtonAnonSortClick(Remetente: TObject);


começar
FListDate.Sort(TComparer<TDate>.Construct(DoCompare));
fim;

note O método DoCompare acima funciona como um método anônimo, mesmo que tenha um nome. Veremos
em um trecho de código posterior que isso não é necessário. Tenha paciência até o próximo capítulo para obter mais
informações sobre esta construção da linguagem Object Pascal. Observe também que com um registro TDate eu
poderia ter definido os operadores menor que e maior que, tornando esse código mais simples, mas mesmo com uma
classe eu poderia ter colocado o código de comparação em um método da classe.

Se isso parecer bastante tradicional, considere que você poderia ter evitado a declaração de uma função separada
e passar a implementação da função na forma de código-fonte como parâmetro para o método Construct ,
como segue:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 411

procedimento TForm1.ButtonAnonSortClick(Remetente: TObject);


começar
ListDate.Sort(TComparer<TDate>.Construct(
função (const Esquerda, Direita: TDate): Inteiro
era
LDate, RDate: TDateTime;
começar
LDate := EncodeDate(Esquerda.Ano, Esquerda.Mês, Esquerda.Dia);
RDate := EncodeDate(Right.Year, Right.Month, Right.Day);
se LDate = RDate então
Resultado:= 0
senão se LDate < RDate então
Resultado:= -1
outro
Resultado:= 1;
fim));
fim;

Este exemplo deve ter aguçado seu apetite para aprender mais sobre métodos anônimos! Com
certeza, esta última versão é muito mais simples de escrever do que a comparação original abordada na
seção anterior, embora para muitos desenvolvedores de Object Pascal tenham
uma classe derivada pode parecer mais limpa e mais fácil de entender (a versão herdada separa
melhor a lógica, facilitando a reutilização potencial do código, mas muitas vezes você não a utilizará
de qualquer maneira).

Contêineres de objetos
Além das classes genéricas abordadas no início desta seção, há também quatro classes genéricas
herdadas que são derivadas das classes base definidas na unidade Generic-s.Collections , imitando as
classes existentes da unidade Contnrs (que significa containers) :

tipo
TObjectList<T: classe> = classe(TList<T>)
TObjectQueue<T: classe> = classe(TQueue<T>)
TObjectStack<T: classe> = classe(TStack<T>)

Em comparação com suas classes base, existem duas diferenças principais. Uma é que esses tipos
genéricos podem ser usados apenas para objetos; a segunda é que eles definem um método de
notificação customizado , que, no caso de um objeto ser removido da lista (além de chamar
opcionalmente o manipulador de eventos OnNotify ), irá liberar o objeto.

Em outras palavras, a classe TObjectList<T> se comporta como sua contraparte não genérica
quando a propriedade OwnsObjects é definida. Se você está se perguntando por que isso não é mais
uma opção, considere que TList<T> agora pode ser usado diretamente para trabalhar com tipos de
objetos, ao contrário de sua contraparte não genérica.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

412 - 14: genéricos

Há também uma quarta classe, novamente, chamada TObjectDictionary<K, V>, que é definida de
uma maneira diferente, pois pode possuir o objeto-chave, os objetos de valor ou ambos. Consulte o
conjunto TDictionaryOwnerships e o construtor da classe para obter mais detalhes.

Usando um dicionário genérico


De todas as classes de contêiner genéricas predefinidas, aquela que provavelmente merece um
estudo mais detalhado é o dicionário genérico, TObjectDictionary<K, V>.

note Dicionário, neste caso, significa uma coleção de elementos, cada um com um valor-chave exclusivo referente a ele. Um
dicionário também é conhecido como array associativo. Em um dicionário tradicional você tem palavras atuando
como chaves para suas definições, mas em termos de programação a chave não precisa ser uma string (mesmo que
este seja um caso bastante frequente). Nas versões anteriores das coleções genéricas do Delphi, os marcadores de
posição do tipo dicionário eram indicados em TKey e TValue. Entretanto, dado que TValue é um tipo de dados
RTL não relacionado (abordado no Capítulo 16), no Delphi 11 eles foram renomeados como K e V, para evitar
confusão. O livro foi atualizado de acordo, como você pode ver acima.

Outras classes são igualmente importantes, mas parecem mais fáceis de usar e entender.
Como exemplo de uso de um dicionário, escrevi um aplicativo que busca dados de um
tabela do banco de dados, cria um objeto para cada registro e usa um índice composto com um
ID do cliente e uma descrição como chave. A razão para esta separação é que uma arquitetura
semelhante pode ser facilmente usada para criar um proxy (inicialização lenta), em que a chave
toma o lugar de uma versão light do objeto real carregado do banco de dados.
A seguir estão as duas classes usadas pelo exemplo CustomerDictionary para a chave e o valor real.
O primeiro possui apenas dois campos relevantes da tabela do banco de dados correspondente,
enquanto o segundo possui a estrutura de dados completa (omiti os campos privados, métodos
getter e métodos setter):

tipo
TCustomerKey = classe
privado
...
Publicados
propriedade CustNo: leitura dupla FCustNo gravação SetCustNo;
propriedade Empresa: string lida FCompany escreve SetCompany;
fim;

TCliente = classe
privado
...
procedimento inicial;
procedimento EnforceInit;
público
construtor Criar (ACustKey: TCustomerKey);
propriedade CustKey: TCustomerKey
ler FCustKey escrever SetCustKey;
Publicados

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 413

propriedade CustNo: leitura dupla


GetCustNo gravação SetCustNo; propriedade
Empresa: string lida GetCompany
escreve SetCompany; propriedade Addr1: string lida
GetAddr1 escreve SetAddr1;
propriedade Cidade: string lida GetCity escreve
SetCity; propriedade Estado:
string ler GetState escrever SetState;
propriedade Zip: string lida GetZip
escreve SetZip;

propriedade País: string lida GetCountry


write SetCountry; propriedade Telefone: string lida
GetPhone escreve SetPhone;
propriedade Fax: string lida GetFax escreve
SetFax;

propriedade Contato: string lida


GetContact write SetContact; classe var

RefDataSet: TDataSet; fim;

Enquanto a primeira classe é muito simples (cada objeto é inicializado quando é criado), a classe
TCustomer usa um modelo de inicialização lenta (ou proxy) e mantém em torno de uma referência ao
banco de dados de origem compartilhada (como uma classe var) por todos. objetos.

note Neste exemplo estou usando um objeto derivado de TDataSet para acessar dados do banco de dados, para tornar este cenário
um pouco mais real. Discutir o acesso ao banco de dados em Delphi está realmente além do escopo deste livro, portanto
não fornecerei muitas descrições adicionais do desempenho real das operações nesta classe TDataSet .

Quando um objeto é criado, é atribuída uma referência ao TCustomerKey correspondente, enquanto um


campo de dados de classe se refere ao conjunto de dados de origem. Em cada método getter, a
classe verifica se o objeto foi realmente inicializado antes de retornar os dados, como no caso a seguir:

função TCustomer.GetCompany: string; comece EnforceInit;

Resultado :=
FEmpresa; fim;

O método EnforceInit verifica um sinalizador local, eventualmente chamando Init para carregar dados
do banco de dados para o objeto na memória:

procedimento TCustomer.EnforceInit; comece, se


não for
FInitDone, então Init; fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

414 - 14: genéricos

procedimento TCustomer.Init;
começar
RefDataSet.Locate( 'CustoNão' , CustKey.CustNo, []);

// Também poderia carregar cada campo publicado via RTTI


FCustNo := RefDataSet.FieldByName(FCompany := 'CustoNão').AsFloat;
RefDataSet.FieldByName(FCountry := 'Empresa' ).Como corda;
RefDataSet.FieldByName( 'País' ).Como corda;
...
FInitDone := Verdadeiro;
fim;

Dadas essas duas classes, adicionei um dicionário de propósito especial ao aplicativo. Esse
a classe de dicionário personalizada herda de uma classe genérica instanciada com os tipos apropriados e adiciona a
ela um método específico:

tipo
TCustomerDictionary = classe(
TObjectDictionary<TCustomerKey, TCustomer>)
público
procedimento LoadFromDataSet(DataSet: TDataSet);
fim;

O método de carregamento preenche o dicionário, copiando dados apenas para os objetos-chave:

procedimento TCustomerDictionary.LoadFromDataSet(DataSet: TDataSet);


era
CustKey: TCustomerKey;
começar
TCustomer.RefDataSet := DataSet;
DataSet.Primeiro;
enquanto não DataSet.EOF faça
começar
CustKey := TCustomerKey.Create;
CustKey.CustNo := 'CustoNão'];
DataSet[CustKey.Company := 'Empresa' ];
DataSet[Self.Add(CustKey, TCustomer.Create(CustKey));
DataSet.Próximo;
fim;
fim;

O programa de demonstração possui um formulário principal e um módulo de dados que hospeda um componente
ClientDataSet. O formulário principal possui um controle ListView que é preenchido quando um usuário pressiona o único
botão no formulário.

note Você pode querer substituir o componente ClientDataSet por um conjunto de dados real, expandindo consideravelmente o
exemplo em termos de utilidade, já que você poderia executar uma consulta para as chaves e uma consulta separada
para os dados reais de cada objeto TCustomer . Eu tenho um código semelhante, mas adicioná-lo aqui nos distrairia
muito do objetivo do exemplo, que é experimentar uma classe de dicionário genérica.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 415

Após carregar os dados no dicionário, o método BtnPopulateClick utiliza um enumerador nas


chaves do dicionário:

procedimento TFormCustomerDictionary.BtnPopulateClick(Sender: TObject);


era
CustKey: TCustomerKey;
ListItem: TListItem;
começar
DataModule1.ClientDataSet1.Active := Verdadeiro;
CustDict.LoadFromDataSet(DataModule1.ClientDataSet1);
para CustKey em CustDict.Keys faça
começar
ListItem := ListView1.Items.Add;
ListItem.Caption := CustKey.Company;
ListItem.SubItems.Add(FloatTOSr(CustKey.CustNo));
ListItem.Data := CustKey;
fim;
fim;
Isso preenche as duas primeiras colunas do controle ListView, com os dados disponíveis nos
objetos-chave. Porém, sempre que um usuário seleciona um item do controle ListView, o programa
preencherá uma terceira coluna:

procedimento TFormCustomerDictionary.ListView1SelectItem(
Remetente: TObject; Item: TListItem; Selecionado: Booleano);
era
ACliente: TCliente;
começar
ACustomer := CustDict.Items[Item.Data];
Item.SubItems.Add(
Se então(
''
ACustomer.State <> ,
'
ACustomer.State + ', + ACliente.País,
ACustomer.Country));
fim;

O método acima mapeia o objeto para a chave fornecida e usa seus dados. Nos bastidores, na
primeira vez que um objeto específico é utilizado, o método de acesso à propriedade aciona o
carregamento de todos os dados do objeto TCustomer .

Dicionários vs. listas de strings


Ao longo dos anos, muitos desenvolvedores de Object Pascal, inclusive eu, usaram
excessivamente a classe TStringList . Você não só pode usá-lo para uma lista simples de strings
e para uma lista de pares nome/valor, mas também pode usá-lo para ter uma lista de objetos associados a stri
e pesquise esses objetos. Desde a introdução dos genéricos, no entanto, é muito melhor usar uma
classe baseada em genéricos em vez de usar TStringList em uma abordagem do tipo canivete suíço.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

416 - 14: genéricos

Classes de contêineres específicas e focadas são uma opção muito melhor. Por exemplo, um genérico
TDictionary com uma chave de string e um valor de objeto geralmente será melhor que um
TStringList em dois aspectos: código mais limpo e seguro, pois haverá menos conversões de tipo
envolvidas e execução mais rápida, visto que os dicionários usam tabelas hash.

Para demonstrar essas diferenças, escrevi um aplicativo bastante simples, chamado


StringListVsDictionary. Seu formulário principal armazena duas listas idênticas, declaradas como:

privado
FList: TStringList;
FDict: TDictionary<string, TMyObject>;

As duas listas são preenchidas com entradas aleatórias, mas idênticas, usando um loop que repete este
código:

FList.AddObject(AName, AnObject);
FDict.Add(AName, AnObject);

Dois botões recuperam cada elemento da lista e fazem uma busca por nome em cada um deles.
Ambos os métodos examinam a lista de strings em busca de valores, mas o primeiro localiza os objetos
na lista de strings, enquanto o segundo usa o dicionário. Observe que no primeiro caso você precisa de
um as cast para recuperar o tipo fornecido, enquanto o dicionário já está vinculado a essa classe.
Aqui está o loop principal dos dois métodos:

OTotal := 0;
para I := 0 para SList.Count - 1 faça
começar
ANome := FLista[I];
// Nai procurar isto
AnIndex := FList.IndexOf(AName);
// GE o objeto
AnObject := FList.Objects[AnIndex] as TMyObject;
Inc(TheTotal, AnObject.Value);
fim;

OTotal := 0;
para I := 0 para FList.Count - 1 faça
começar
ANome := FLista[I];
// GE o objeto
AnObject := FDict.Items[AName];
Inc(TheTotal, AnObject.Value);
fim;

Não quero acessar as strings em sequência, mas descobrir quanto tempo leva para pesquisar na lista de
strings classificada (que faz uma pesquisa binária) em comparação com as chaves hash do dicionário.
Não é de surpreender que o dicionário seja mais rápido. Aqui estão os números em milissegundos para
um teste:

Total: 99493811
StringList: 2839
Total: 99493811

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 417

Dicionário: 686

Os totais eram obviamente idênticos, mas os tempos são bastante diferentes, com o dicionário
demorando cerca de um quarto do tempo para cerca de 100 milhões de entradas.

Interfaces genéricas

Na seção “Classificando um TList<T>” você deve ter notado o uso um tanto estranho de uma
interface predefinida, que tinha uma declaração genérica. Vale a pena examinar detalhadamente
essa técnica, pois ela abre oportunidades significativas.
O primeiro elemento técnico a notar é que é perfeitamente legal definir uma interface genérica,
como fiz no exemplo GenericInterface :

tipo
IGetValue<T> = interface
função ObterValor: T;
procedimento SetValue(Valor: T);
fim;

note Esta é a versão genérica da interface IGetValue do exemplo IntfContraints , abordada na seção
anterior “Restrições de interface” deste capítulo. Nesse caso a interface tinha um valor Inteiro,
agora tem um valor genérico.

Observe que, diferentemente de uma interface padrão, no caso de uma interface genérica, não
é necessário especificar um GUID para ser usado como Interface ID (ou IID). O compilador irá
gerar um IID para você para cada instância da interface genérica, mesmo que declarada
implicitamente. Na verdade, você não precisa criar uma instância específica da interface genérica
para implementá-la, mas pode definir uma classe genérica que implemente a interface genérica:

tipo
TGetValue<T> = classe(TInterfacedObject, IGetValue<T>)
privado
Valor F: T;
público
construtor Criar (Valor: T);
destruidor Destruir; sobrepor;
função ObterValor: T;
procedimento SetValue(Valor: T);
fim;

Embora o construtor atribua o valor inicial do objeto, o único propósito do destruidor é registrar
que um objeto foi destruído. Podemos criar uma instância desta classe genérica (gerando uma
instância específica do tipo de interface nos bastidores) escrevendo:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

418 - 14: genéricos

procedimento TFormGenericInterface.BtnValueClick(Sender: TObject);


era
AVal: TGetValue<string>;
começar
AVal := TGetValue<string>.Create(Caption);
tentar
'
'Valor TGetValue: + AVal.GetValue);
Mostrar(finalmente
AVal.Livre;
fim;
fim;

Uma abordagem alternativa, como vimos no passado para o exemplo IntfConstraint , é usar uma variável
de interface do tipo correspondente, tornando explícita a definição específica do tipo de interface (e não
implícita como no trecho de código anterior):

procedimento TFormGenericInterface.BtnIValueClick(Sender: TObject);


era
AVal: IGetValue<string>;
começar
AVal := TGetValue<string>.Create(Caption);
'
'Valor
Mostrar(+ IGetValue: //
AVal.GetValue);
Liberado automaticamente, como isso é referência contada
fim;

Claro, também podemos definir uma classe específica que implemente a interface genérica, como no
cenário a seguir (do exemplo GenericInterface ):

tipo
TButtonValue = classe(TButton, IGetValue<Integer>)
público
função GetValue: Inteiro;
procedimento SetValue(Valor: Inteiro);
função de classe MakeTButtonValue (Proprietário: TComponent;
Pai: TWinControl): TButtonValue;
fim;

Observe que enquanto a classe genérica TGetValue<T> implementa a classe genérica IGetValue<T>
interface, a classe específica TButtonValue implementa a classe específica IGetValue<Integer>
interface. Especificamente, como no exemplo anterior, a interface é remapeada para a esquerda
propriedade do controle:

função TButtonValue.GetValue: Inteiro;


começar
Resultado := Esquerda;
fim;

Na classe acima, a função da classe MakeTButtonValue é um método pronto para usar para criar um
objeto da classe. Este método é utilizado pelo terceiro botão do formulário principal, conforme segue:

procedimento TFormGenericInterface.BtnValueButtonClick(Sender: TObject);


era

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 419

IVal: IGetValue<Inteiro>;
começar
IVal := TButtonValue.MakeTButtonValue(Self, ScrollBox1);
'
'Valor do botão:
Mostrar(+IntToStr(IVal.GetValue));
fim;

Embora não tenha nenhuma relação com classes genéricas, aqui está a implementação do
Função da classe MakeTButtonValue :

função de classe TButtonValue.MakeTButtonValue (


Proprietário: TComponent; Pai: TWinControl): TButtonValue;
começar
Resultado: = TButtonValue.Create (Proprietário);
Resultado.Parent := Pai;
Resultado.SetBounds(Random(Parent.Width),
Random(Parent.Height), Result.Width, Result.Height);
Resultado.Texto := 'btnv' ;
fim;

Interfaces genéricas predefinidas

Agora que exploramos como definir interfaces genéricas e combiná-las com o uso de classes
genéricas e específicas, podemos voltar a dar uma segunda olhada na unidade Generics.Defaults .
Esta unidade define duas interfaces genéricas de comparação:
· IComparer<T> possui um método Compare
· IEqualityComparer<T> possui métodos Equals e GetHashCode
Estas classes são implementadas por algumas classes genéricas e específicas, listadas abaixo
(sem detalhes de implementação):

tipo
TComparer<T> = classe(TInterfacedObject, IComparer<T>)
TEqualidadeComparer<T> = class(
TInterfacedObject, IEqualityComparer<T>)
TCustomComparer<T> = class(TSingletonImplementation,
IComparer<T>, IEqualityComparer<T>)
TStringComparer = classe(TCustomComparer<string>)

Na listagem acima você pode ver que a classe base usada pelas implementações genéricas
das interfaces é a classe TInterfacedObject contada por referência clássica ou a classe
TSingletonImplementation (o alias da classe TNoRefCountObject ). Isto é um
classe com nome estranho que fornece uma implementação básica de IInterface sem contagem
de referências.

note O termo singleton geralmente é usado para definir uma classe da qual você pode criar apenas uma instância, e não
uma sem contagem de referência. Considero isso um nome bastante impróprio.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

420 - 14: genéricos

Como já vimos na seção “Classificando um TList<T>”, anteriormente neste capítulo, essas classes de
comparação são usadas pelos contêineres genéricos. Para tornar as coisas mais complicadas, porém,
a unidade Generics.Default depende bastante de métodos anônimos, então você provavelmente deveria
dar uma olhada nela somente depois de ler o próximo capítulo.

Ponteiros inteligentes em Object Pascal


Ao abordar genéricos, você pode ter a primeira impressão errada de que essa construção de linguagem
é usada principalmente para coleções. Embora este seja o caso mais simples para o uso de classes
genéricas, e muitas vezes o primeiro exemplo em livros e documentos, os genéricos são úteis muito além do
domínio das classes de coleção (ou contêiner). Como último exemplo deste capítulo, mostrarei um tipo
genérico que não pertence a uma coleção : uma definição de ponteiro inteligente.

Se você tem experiência em Object Pascal, talvez não tenha ouvido falar de ponteiros inteligentes,
uma ideia que vem da linguagem C++. Em C++ você pode ter ponteiros para objetos, para os quais é
necessário gerenciar a memória direta e manualmente, e variáveis de objetos locais que são gerenciadas
automaticamente, mas têm muitas outras limitações (incluindo a falta de polimorfismo). A ideia de um
ponteiro inteligente é usar um objeto gerenciado localmente para cuidar da vida útil do ponteiro para o
objeto real que você deseja usar. Se este
parece muito complicado, espero que a versão Object Pascal ajude a esclarecer isso.

note O termo polimorfismos em linguagens OOP é usado para denotar a situação em que você atribui a uma
variável de uma classe base um objeto de uma classe derivada e chama um dos métodos virtuais da
classe base, potencialmente acabando chamando a versão do método virtual da subclasse específica.

Usando registros para ponteiros inteligentes


Em Object Pascal, os objetos são gerenciados por referência, enquanto os registros têm um limite de vida útil
ao método em que são declarados. Quando o método termina, a área de memória para
o registro é limpo. Então o que podemos fazer é usar um registro para gerenciar o tempo de vida de um
objeto Object Pascal.

Antes do Delphi 10.4, os registros Object Pascal não ofereciam nenhuma maneira de executar código
personalizado no momento da destruição, um recurso introduzido nos registros gerenciados. Em vez
disso, o método antigo era usar um campo de interface nos registros, já que esse campo de interface é
gerenciado e o objeto usado para implementar a interface tem sua contagem de referências diminuída.

Outra consideração é se queremos usar um registro padrão ou genérico.


Com um registro padrão tendo um campo do tipo TObject, você pode excluir esse objeto quando

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 421

necessário, o que é suficiente em termos gerais. Com uma versão genérica, porém, você pode obter
duas vantagens:

· Um ponteiro inteligente genérico pode retornar uma referência ao objeto que contém, para que você
não precisa manter as duas referências por perto
· Um ponteiro inteligente genérico pode criar automaticamente o objeto contêiner, usando um
construtor sem parâmetros.
Aqui abordarei apenas dois exemplos de ponteiros inteligentes implementados usando registros
genéricos, mesmo que isso adicione um pouco mais de complexidade. O ponto de partida será um
registro genérico com restrição de objetos, como:

tipo
TSmartPointer<T:class>=registro
estritamente privado
Valor F: T;
função ObterValor: T;
público
construtor Criar (AValue: T);
Valor da propriedade: T read GetValue;
fim;

Os métodos Create e GetValue do registro simplesmente atribuem e leem o valor.


O cenário de caso de uso é o seguinte trecho de código que cria um objeto, cria um ponteiro
inteligente que o envolve e também permite usar o ponteiro inteligente para se referir ao objeto
incorporado e chamar seus métodos (veja a última linha de código abaixo):

era
SL: ListaString;
começar
SL := TStringList.Create;
var SmartP: TSmartPointer<TStringList>.Create(SL);
'Foo'
SL.Adicionar();
SmartP.Value.Add( 'Bar' );

Como você deve ter percebido, esse código causa um vazamento de memória exatamente da mesma
maneira que sem o ponteiro inteligente! Isso acontece porque o registro é destruído ao sair do escopo,
mas não libera o objeto interno.

Implementando um ponteiro inteligente com um genérico


Registro gerenciado
Embora a operação mais relevante do registro do ponteiro inteligente seja sua finalização, no código
abaixo, como você pode ver, também adicionei um operador de inicialização para definir a referência
do objeto como nula. Idealmente, evitaríamos qualquer operação de atribuição (pois ter vários links
para os objetos internos exigiria a configuração de uma referência bastante complexa).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

422 - 14: genéricos

mecanismo de contagem), mas como isso não é possível, adicionei o operador e o


implementei para gerar uma exceção caso ele seja acionado.
Este é o código completo do registro gerenciado genérico:

tipo
TSmartPointer<T: classe, construtor> = registro estritamente privado

Valor F: T;
função ObterValor: T; operador
de classe
pública Initialize(out ARec: TSmartPointer<T>); operador de classe Finalize(var
ARec: TSmartPointer<T>); operador de classe Assign(var ADest:
TSmartPointer<T>; const [ref] ASrc: TSmartPointer<T>); construtor Criar
(AValue: T); Valor da propriedade: T read GetValue;
fim;

Observe que, além da restrição de classe, o registro genérico também possui uma restrição
de construtor porque quero ser capaz de criar objetos do tipo de dados genérico. Isso acontece
caso o método GetValue seja chamado e o campo ainda não tenha sido inicializado. Este é
o código completo de todos os métodos:

construtor TSmartPointer<T>.Create(AValue: T); começar FValue :=


AValue;
fim;

operador de classe TSmartPointer<T>.Initialize( out ARec:


TSmartPointer<T>); começar

ARec.FValue := nil; fim;

operador de classe TSmartPointer<T>.Finalize( var ARec:


TSmartPointer<T>); comece

ARec.FValue.Free; fim;

operador de classe TSmartPointer<T>.Assign( var ADest:


TSmartPointer<T>; const [ref] ASrc:
TSmartPointer<T>); começar a aumentar

Exception.Create(end; 'Não pode copiar ou atribuir TSmartPointer<T>a ');

função TSmartPointer<T>.GetValue: T; comece se não


for
atribuído (FValue), então FValue: =
T.Create; Resultado :=
FValor; fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 423

Este código faz parte do projeto SmartPointersMR , que inclui também um exemplo de como o
ponteiro inteligente pode ser usado. O primeiro se assemelha estritamente ao código de exemplo que
consideramos algumas páginas atrás:

procedimento TFormSmartPointers.BtnSmartClick(Sender: TObject);


era
SL: ListaString;
começar
SL := TStringList.Create;
var SmartP := TSmartPointer<TStringList>.Create(SL);
'Foo'
SL.Adicionar();
SmartP.Value.Add(Log(end;'Bar' );
'
'Contar: + SL.Count.ToString);

Porém, como o ponteiro inteligente genérico tem suporte para construção automática de um objeto do
tipo indicado, você também pode usar a variável explícita referente à lista de strings e o código para
criá-la:

procedimento TFormSmartPointers.BtnSmartShortClick(Sender: TObject);


era
SmartP: TSmartPointer<TStringList>;
começar
'Foo' );
'Bar' );
'
'Contar: + SmartP.Value.Count.ToString);
SmartP.Value.Add(SmartP.Value.Add(Log(end;

No programa, você pode verificar se todos os objetos foram realmente destruídos e se não há
vazamento de memória definindo o ReportMemoryLeaksOnShutdown global como True no código de
inicialização. Como contra-teste, existe um botão no programa que causa um vazamento, que
será detectado quando o programa for encerrado.

Implementando um ponteiro inteligente com um genérico


Registro e uma interface

Como já mencionei que, antes do Delphi 10.4 disponibilizar os registros gerenciados, uma forma
possível de implementar ponteiros inteligentes era a utilização de uma interface, visto que o registro
liberará automaticamente um objeto referenciado por um campo de interface. Embora esta
abordagem seja agora menos interessante, ela oferece alguns recursos adicionais, como
operadores de conversão implícitos.

Dado que este continua a ser um exemplo interessante e complexo, decidi mantê-lo, com uma
descrição um tanto reduzida (este é o projeto SmartPointers na fonte).
Para implementar um ponteiro inteligente com uma interface, você pode escrever uma classe de
suporte interna, vinculada a uma interface, e usar o mecanismo de contagem de referência de
interface para determinar quando liberar o objeto. A classe interna se parece com o seguinte:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

424 - 14: genéricos

tipo
TFreeTheValue = classe(TInterfacedObject) privado

FObjectToFree: TObject; construtor


público
Create(AnObjectToFree: TObject); destruidor Destruir; sobrepor; fim;

construtor TFreeTheValue.Create(AnObjectToFree: TObject); começar FObjectToFree :=

AnObjectToFree; fim;

destruidor TFreeTheValue.Destroy; comece

FObjectToFree.Free; herdado;
fim;

Declarei isso como um tipo aninhado do tipo genérico de ponteiro inteligente. Tudo o que precisamos fazer
no tipo genérico de ponteiro inteligente, para habilitar esse recurso, é adicionar uma referência de
interface e inicializá-la com um objeto TFreeTheValue referente ao objeto contido:

tipo
TSmartPointer<T:class> = registro estritamente
privado
Valor F: T;
FFreeTheValue: IInterface; função
ObterValor: T; construtor público
Criar
(AValue: T); sobrecarga; Valor da propriedade: T read GetValue;
fim;

O pseudoconstrutor se torna:

construtor TSmartPointer<T>.Create(AValue: T); começar FValue := AValue;

FFreeTheValue :=
TFreeTheValue.Create(FValue); fim;

Com esse código implementado, agora podemos escrever o seguinte código em um programa sem
causar vazamento de memória (novamente, o código é semelhante ao que listei inicialmente e usei na
versão de registro do gerenciador):

procedimento TFormSmartPointers.BtnSmartClick(Sender: TObject);


era
SL: ListaString;
SmartP: TSmartPointer<TStringList>; começar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 425

SL := TStringList.Create;
SmartP.Create (SL);
'Foo'
SL.Adicionar();
'Bar'
SL.Adicionar();
'Contar: ' + IntToStr(SL.Count));
Mostrar(fim;

Ao final do método o registro SmartP é descartado, o que faz com que seu objeto de interface
interna seja destruído, liberando o objeto TStringList .

note O código funciona mesmo se uma exceção for gerada. Na verdade, blocos try-finally implícitos estão sendo adicionados
sobre os locais pelo compilador quando você usa um tipo gerenciado, como um registro com um campo de
interface (que é o cenário específico aqui).

Adicionando conversão implícita


Com a solução de registros gerenciados, precisamos tomar cuidado extra para evitar registros
operações de cópia, pois isso exigiria a adição de um mecanismo manual de contagem de
referências e tornaria a estrutura muito mais complexa. No entanto, dado que isto está
incorporado na solução baseada em interface, podemos aproveitar este modelo para
adicionar operadores de conversão, o que pode simplificar a inicialização e criação da estrutura de
dados. Especificamente, adicionarei um operador de conversão implícita para atribuir o objeto de
destino ao ponteiro inteligente:

operador de classe TSmartPointer<T>.Implicit(AValue: T): TSmartPointer<T>;


começar
Resultado:=TSmartPointer<T>.Create(AValue);
fim;

Com este código (e aproveitando o campo Valor ) podemos agora escrever uma versão mais
compacta do código, como:
era
SmartP: TSmartPointer<TStringList>;
começar
SmartP := TStringList.Create;
SmartP.Value.Add(); 'Foo'
SmartP.Value.Add(); 'Bar'
'Contar: '
Mostrar(+IntToStr(SmartP.Value.Count));

Como alternativa, podemos usar uma variável TStringList e usar um construtor mais complicado
para inicializar o registro do ponteiro inteligente mesmo sem uma referência explícita a ele:
era
SL: ListaString;
começar
SL := TSmartPointer<TStringList>.Create(TStringList.Create).Value;
SL.Adicionar( 'Foo' );

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

426 - 14: genéricos

'Bar' );
'Contar: ' + IntToStr(SL.Count));
SL.Add(Mostrar(
Ao iniciarmos esse caminho, também podemos definir a conversão oposta e usar a notação de conversão
em vez da propriedade Value :

operador de classe TSmartPointer<T>.Implicit(AValue: T): TSmartPointer<T>;


começar
Resultado:=TSmartPointer<T>.Create(AValue);
fim;

era
SmartP: TSmartPointer<TStringList>;
começar
SmartP := TStringList.Create;
TStringList(SmartP).Add(); 'Bar'

Agora, você também pode notar que sempre usei um pseudoconstrutor no código acima, mas isso
não é necessário em um registro. Tudo o que precisamos é de uma forma de inicializar o objeto interno,
possivelmente chamando seu construtor, na primeira vez que o usarmos.

Não podemos testar se o objeto interno está atribuído, porque os registros (ao contrário das classes) não
são inicializados com zero. Porém podemos realizar esse teste na variável de interface, que é
inicializada. Como alternativa, poderíamos inicializar o registro com zero com código construtor extra.

Comparando soluções de ponteiro inteligente


A versão de registro gerenciado do ponteiro inteligente é mais simples e bastante eficaz; a versão
baseada em interface, entretanto, oferece a vantagem dos operadores de conversão. Ambos têm mérito,
embora, pessoalmente, eu tenha tendência a preferir a versão de registro gerenciado.

note Para uma análise mais articulada e soluções mais sofisticadas (fora do escopo deste livro), posso
recomendar a seguinte postagem no blog de Erik van Bilsen: https://blog.grijjy.com/2020/08/12/custom-
owned -registros-para-ponteiros-inteligentes/.

Tipos de retorno covariante com genéricos


Em geral, em Object Pascal (e na maioria das outras linguagens estáticas orientadas a objetos), um método
pode retornar um objeto de uma classe, mas não pode substituí-lo em uma classe derivada para retornar
um objeto de classe derivada. Esta é uma prática bastante comum chamada “Tipo de retorno covariante”
e explicitamente suportada por algumas linguagens como C++.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 427

Sobre animais, cães e gatos


Em termos de codificação, se TDog herdar de TAnimal, eu gostaria de ter os métodos:

função TAnimal.Get (AName: string): TAnimal;


função TDog.Get (Nome: string): TDog;

No Object Pascal você não pode ter funções virtuais com valor de retorno diferente, nem sobrecarregar
o tipo de retorno, mas apenas quando estiver usando parâmetros diferentes. Deixe-me mostrar o código
completo de uma demonstração simples que envolve as três classes a seguir:

tipo
TAnimal = classe
privado
NomeF: string;
procedimento SetName(const Valor: string);
público
nome da propriedade: string lida FName escreve SetName;
público
função de classe Get (const AName: string): TAnimal;
função ToString: string; sobrepor;
fim;

TDog = classe(TAnimal)
fim;

TCato = classe(TAnimal)
fim;

A implementação dos dois métodos é bastante simples, uma vez que você percebe que a função da
classe é na verdade utilizada para criar novos objetos, chamando internamente um construtor.

A razão é que não quero criar um construtor diretamente porque esta é uma técnica mais geral, na qual
um método de uma classe pode criar objetos de outras classes (ou hierarquias de classes).

Este é o código:

função de classe TAnimal.Get (const AName: string): TAnimal;


começar
Resultado := Criar;
Resultado.FNome := ANome;
fim;

função TAnimal.ToString: string;


começar
'
Resultado := 'Isso + Copiar(ClassName, 2, MaxInt) +
' '
se chama + FNome;
fim;

Agora podemos usar a classe escrevendo o seguinte código, do qual não gosto muito, já que
precisamos converter o resultado para o tipo adequado:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

428 - 14: genéricos

era
ACato: TCato;
começar
'Matisse'
ACat := TCat.Get() de TCat;
Memo1.Lines.Add(ACat.ToString);
AGato.Livre;

Dado que o código garante que Get retorne um objeto TCat , a primeira linha acima também pode
ser escrita como a seguir (executando um pouco mais rápido):

ACat := TCat(TCat.Get( 'Matisse' ));

Novamente, o que eu gostaria de fazer é poder atribuir o valor retornado por TCat.Get a uma referência
da classe TCat sem uma conversão explícita. Como podemos fazer isso?

Um método com resultado genérico

Acontece que os genéricos podem nos ajudar a resolver o problema. Não são tipos genéricos,
que é a forma de genérico mais comumente usada. Mas métodos genéricos para tipos não genéricos,
discutidos anteriormente neste capítulo. O que posso adicionar à classe TAnimal é um método com
um parâmetro de tipo genérico , como:

função de classe GetAs<T: classe>(const AName: string): T;

Este método requer um parâmetro de tipo genérico, que precisa ser uma classe (ou tipo de instância)
e retorna um objeto desse tipo. Este é um exemplo de implementação:

função de classe TAnimal.GetAs<T>(const AName: string): T;


era
Resposta: TAnimal;
começar
Res := Get(ANome);
se Res.InheritsFrom(T) então
Resultado := T(Res)
outro
Resultado := nulo;
fim;

Agora podemos criar uma instância e utilizá-la omitindo o as cast, embora ainda tenhamos que passar
o tipo como parâmetro:

era
ADão: TDog;
começar
ADog := 'Plutão' );
TDog.GetAs<TDog>(Memo1.Lines.Add(ADog.ToString);
ADog.Free;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

14: genéricos - 429

Retornando um objeto derivado de uma classe diferente


Ao retornar um objeto da mesma classe, você pode substituir esse código pelo uso adequado de
construtores. Mas o uso de genéricos para obter tipos de retorno covariantes é, na verdade, mais flexível.
Na verdade, podemos usá-lo para retornar objetos de uma classe ou hierarquia de classes diferente:

tipo
TAnimalShop = classe
função de classe GetAs<T: TAnimal, construtor>(
const AName: string): T;
fim;

note Uma classe como esta, usada para criar objetos de uma classe diferente (ou mais de uma classe, dependendo do
parâmetro ou parâmetros) é geralmente chamada de “fábrica de classes”.

Agora podemos usar a restrição de classe específica (algo impossível na própria classe) especificando a
restrição do construtor para poder criar um objeto da classe dada a partir do método genérico:

função de classe TAnimalShop.GetAs<T>(const AName: string): T;


era
Resposta: TAnimal;
começar
Res := T.Criar;
Res.Nome := ANome;
se Res.InheritsFrom(T) então
Resultado := T(Res)
outro
Resultado := nulo;
fim;

Observe que agora, na chamada, não precisamos repetir o tipo de classe duas vezes:

ADog := TanimalShop.GetAs<TDog>( 'Plutão' );

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

430 - 15: métodos anônimos

15: anônimo
métodos

A linguagem Object Pascal inclui tipos procedurais (ou seja, tipos que declaram ponteiros para
procedimentos e funções) e ponteiros de método (ou seja, tipos que declaram ponteiros para
métodos).

note Caso você queira mais informações, os tipos procedurais foram abordados no Capítulo 4, enquanto eventos e
tipos de ponteiro de método foram descritos no Capítulo 10.

Embora você raramente os use diretamente, esses são os principais recursos do Object Pascal com
os quais todo desenvolvedor trabalha. Na verdade, os tipos de ponteiros de método são a base para
manipuladores de eventos em componentes e controles visuais: toda vez que você declara um
manipulador de eventos, mesmo um simples Button1Click você está na verdade declarando um
método que será conectado a um evento (o evento OnClick , em neste caso) usando um ponteiro de método.

Os métodos anônimos estendem esse recurso permitindo que você passe o código real de um
método como parâmetro, em vez do nome de um método definido em outro lugar. Esta não é a única
diferença, no entanto. O que torna os métodos anônimos muito diferentes de outras técnicas é a
maneira como eles gerenciam o tempo de vida das variáveis locais.

A definição acima corresponde a um recurso chamado encerramentos em muitas outras linguagens,


como JavaScript. Se os métodos anônimos do Object Pascal são de fato fechamentos, como é que
a linguagem se refere a eles usando um termo diferente? A razão reside no fato de ambos

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 431

termos são usados por diferentes linguagens de programação e que o compilador C++
produzido pela Embarcadero usa o termo encerramentos para o que Object Pascal
chama de manipuladores de eventos. Métodos anônimos existem em diferentes formas e com
nomes diferentes há muitos anos em algumas linguagens de programação, principalmente
em linguagens dinâmicas. Tenho vasta experiência com encerramentos em JavaScript,
principalmente com a biblioteca jQuery. O recurso correspondente em C# é chamado de delegado anônim
Mas aqui não quero dedicar muito tempo comparando encerramentos e técnicas relacionadas
nas diversas linguagens de programação, mas sim descrever em detalhes como eles
funcionam no Object Pascal.

note De uma perspectiva muito elevada, os genéricos permitem que o código seja parametrizado para um tipo, enquanto os anônimos
métodos permite que o código seja parametrizado para um método.

Sintaxe e Semântica do Anônimo


Métodos
Um método anônimo em Object Pascal é um mecanismo para criar um valor de método em
um contexto de expressão. Uma definição um tanto enigmática, mas bastante precisa, dada a
sublinha a principal diferença dos ponteiros de método, o contexto da expressão. Antes de
chegarmos a isso, porém, deixe-me começar do início com um exemplo de código muito
simples (incluído no exemplo AnonymFirst junto com a maioria dos outros trechos de código
nesta seção). Esta é a declaração de um tipo de método anônimo, algo que você precisa
fornecer, visto que Object Pascal é uma linguagem fortemente tipada:

tipo
TIntProc = referência ao procedimento(N: Inteiro);

Isso é diferente de um tipo de referência de método apenas nas palavras-chave usadas para a declaração:

tipo
TIntMethod = procedimento(N: Inteiro) do objeto;

Uma variável de método anônimo


Depois de ter um tipo de método anônimo, você pode, nos casos mais simples, declarar uma
variável desse tipo, atribuir um método anônimo compatível com o tipo e chamar o método
através da variável:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

432 - 15: métodos anônimos

procedimento TFormAnonymFirst.BtnSimpleVarClick(Sender: TObject);


era
AnIntProc: TIntProc; começar

AnIntProc :=
procedimento(N: Inteiro) começar

Memo1.Lines.Add(IntToStr(N)); fim; AnIntProc(22);


fim;

Observe a sintaxe usada para atribuir um procedimento real, com código local, à variável local AnIntProc.

Um parâmetro de método anônimo


Como exemplo mais interessante (com sintaxe ainda mais surpreendente), podemos passar um
método anônimo como parâmetro para uma função. Suponha que você tenha uma função que recebe
um parâmetro de método anônimo:

procedimento CallTwice(Valor: Inteiro; AnIntProc: TIntProc); começar

AnIntProc(Valor);
Inc(Valor);
AnIntProc(Valor); fim;

A função chama o método passado como parâmetro duas vezes com dois valores inteiros
consecutivos, o passado como parâmetro e o seguinte. Você chama a função passando um
método anônimo real para ela, com um código surpreendente diretamente no local:

procedimento TFormAnonymFirst.BtnProcParamClick(Sender: TObject); começar CallTwice(48,

procedimento(N: Inteiro)
começar Mostrar(IntToHex(N,
4));
fim); CallTwice(100,

procedimento(N:
Inteiro) começar

Show(FloatToStr(Sqrt(N))); fim); fim;

Do ponto de vista da sintaxe, observe que o procedimento é passado como parâmetro entre
parênteses e não termina com ponto e vírgula. O efeito real do código é chamar IntToHex com 48 e
49 e FloatToStr na raiz quadrada de 100 e 101, produzindo a seguinte saída:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 433

0030
0031
10
10.0498756211209

Usando variáveis locais


Poderíamos ter alcançado o mesmo efeito usando ponteiros de método, embora com uma sintaxe
diferente e menos legível. O que torna o método anônimo claramente diferente é a maneira como
eles podem se referir às variáveis locais do método de chamada. Considere o seguinte código:

procedimento TFormAnonymFirst.BtnLocalValClick(Sender: TObject);


era
Número: Inteiro;
começar
UMNúmero := 0;
Chamarduas vezes(10,
procedimento (N: inteiro)
começar
Inc(ANúmero, N);
fim);
Mostrar(IntToStr(ANumber));
fim;

Aqui o método, ainda passado para o procedimento CallTwice , utiliza o parâmetro local N, mas
também uma variável local do contexto a partir do qual foi chamado, ANumber. Qual é o efeito? As
duas chamadas do método anônimo modificarão a variável local, adicionando o parâmetro a ela,
10 na primeira vez e 11 na segunda vez. O valor final de ANumber
serão 21.

Estendendo a vida útil de variáveis locais


O exemplo anterior mostra um efeito interessante, mas com uma sequência de chamadas de
funções aninhadas, o fato de você poder usar a variável local não é tão surpreendente. O poder
dos métodos anônimos, entretanto, reside no fato de que eles podem usar uma variável local e
também estender seu tempo de vida até que seja necessário. Um exemplo provará isso mais do
que uma longa explicação.

note Em detalhes um pouco mais técnicos, os métodos anônimos copiam as variáveis e os parâmetros que
usam para o heap quando são criados e os mantêm ativos enquanto a instância específica do anônimo
método.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

434 - 15: métodos anônimos

Adicionei (usando o preenchimento de classe) à classe de formulário TFormAnonymFirst do


Exemplo de AnonymFirst, uma propriedade de um tipo de ponteiro de método anônimo (bem, na verdade,
o mesmo tipo de ponteiro de método anônimo que usei em todo o código do projeto):

privado
FAnonMeth: TIntProc;
procedimento SetAnonMeth (valor const: TIntProc);
público
propriedade AnonMeth: TintProc
leia FAnonMeth escreva SetAnonMeth;

Depois adicionei mais dois botões ao formulário do programa. O primeiro salva a propriedade, um método
anônimo que usa uma variável local, mais ou menos como no método BtnLocalValClick anterior:

procedimento TFormAnonymFirst.BtnStoreClick(Sender: TObject);


era
Número: Inteiro;
começar
UMNúmero := 3;
AnonMeth :=
procedimento (N: inteiro)
começar
Inc(ANúmero, N);
Mostrar(IntToStr(ANumber));
fim;
fim;

Quando este método é executado, o método anônimo não é executado, apenas armazenado. A variável
local ANumber é inicializada com três, não é modificada, sai do escopo local (conforme o método termina)
e é deslocada. Pelo menos, é isso que você esperaria do código Object Pascal padrão.

O segundo botão que adicionei ao formulário para esta etapa específica chama o método anônimo
armazenado na propriedade AnonMeth :

procedimento TFormAnonymFirst.BtnCallClick(Sender: TObject);


começar
se atribuído (AnonMeth) então
começar
CallTwice(2, AnonMeth);
fim;
fim;

Quando esse código é executado, ele chama um método anônimo que usa a variável local ANumber de
um método que não está mais na pilha. No entanto, como os métodos anônimos capturam seu
contexto de execução, a variável ainda está lá e pode ser usada enquanto a instância específica do
método anônimo (ou seja, uma referência ao método) estiver disponível.

Como prova adicional, faça o seguinte. Pressione o botão Store uma vez, o botão Call duas vezes e você
verá que a mesma variável capturada está sendo usada:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 435

5
8
10
13

note A razão para esta sequência é que o valor começa em 3, cada chamada para CallTwice passou seu parâmetro para os métodos
anônimos uma primeira vez (que é 2) e depois uma segunda vez após incrementá-lo (que
é, na segunda vez que passa 3).

Agora pressione Armazenar mais uma vez e pressione Ligar novamente. O que acontece, por que
o valor da variável local é redefinido? Ao atribuir uma nova instância de método anônimo, o
método anônimo antigo é excluído (junto com seu próprio contexto de execução) e um novo contexto
de execução é capturado, incluindo uma nova instância da variável local. A sequência completa
Armazenar – Chamar – Chamar – Armazenar – Chamar produz:

5
8
10
13
5
8

É a implicação desse comportamento, semelhante ao que algumas outras linguagens fazem, que
torna os métodos anônimos um recurso de linguagem extremamente poderoso, que você pode
usar para implementar algo que simplesmente não era possível no passado.

Métodos anônimos por trás do


Cenas
Se o recurso de captura de variáveis é um dos mais relevantes para métodos anônimos, existem
mais algumas técnicas que valem a pena examinar, antes de nos concentrarmos em alguns
exemplos do mundo real. Ainda assim, se você é novo nos métodos anônimos, talvez queira pular
esta seção bastante avançada e voltar durante uma segunda leitura.

O parêntese (potencialmente) ausente


Observe que no código acima usei o símbolo AnonMeth para me referir ao método anônimo, não
para invocá-lo. Para invocá-lo, eu deveria ter digitado:

AnonMeth(2)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

436 - 15: métodos anônimos

A diferença é clara; Preciso passar um parâmetro adequado para invocar o método. Coisas
são um pouco mais confusos com métodos anônimos sem parâmetros. Se você declarar:

tipo
TAnyProc = referência ao procedimento;
era
AnyProc: TAnyProc;

A chamada para AnyProc deve ser seguida por parênteses vazios, caso contrário o compilador
pensa que você está tentando obter o método (seu endereço) em vez de chamá-lo:

AnyProc();

Algo semelhante acontece quando você chama uma função que retorna um método anônimo, como
no seguinte caso retirado do exemplo usual do AnonymFirst :

função GetShowMethod: TIntProc;


era
X: Inteiro;
começar
X := Aleatório(100);
'
Mostrar mensagem( 'Novo x é + IntToStr(X));
Resultado :=
procedimento (N: inteiro)
começar
X := X + N;
ShowMessage(IntToStr(X));
fim;
fim;

Agora a questão é: como você chama isso? Se você simplesmente ligar

GetShowMethod;

ele compila e executa, mas tudo o que faz é chamar o código de atribuição de método anônimo,
jogando fora o método anônimo retornado pela função.

Como você chama o método anônimo real passando um parâmetro para ele? Uma opção é
para usar uma variável de método anônima temporária:

era
IP: TIntProc;
começar
IP := GetShowMethod(); // Os parênteses são necessários!!!
PI(3);

Observe neste caso os parênteses após a chamada GetShowMethod . Se você os omitir (uma prática
padrão do Pascal), receberá o seguinte erro:

E2010 Tipos incompatíveis: 'TIntProc' e 'Procedure'

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 437

Sem os parênteses, o compilador pensa que você deseja atribuir o GetShowMethod


função em si, e não seu resultado para o ponteiro do método IP . Ainda assim, usar uma variável
temporária pode não ser a melhor opção neste caso, pois torna o código estranhamente
complexo. Uma simples chamada

GetShowMethod(3);

não compilará, pois você não pode passar um parâmetro para o método. Você precisa adicionar
o parêntese vazio à primeira chamada e o parâmetro Integer ao método anônimo resultante.
Curiosamente, você pode escrever:

GetShowMethod()(3);

Implementação de métodos anônimos


O que acontece nos bastidores da implementação de métodos anônimos? O código real gerado pelo
compilador para métodos anônimos é baseado em interfaces, com um único método de invocação
(oculto) chamado Invoke, além do suporte usual de contagem de referências (que é útil para
determinar o tempo de vida de métodos anônimos e o contexto que eles capturam). .

Obter detalhes internos é provavelmente muito complicado e de valor limitado. Basta dizer que a
implementação é muito eficiente, em termos de velocidade, e requer cerca de 500 bytes extras
para cada método anônimo.
Em outras palavras, uma referência de método em Object Pascal é implementada com uma interface
especial de método único, com um método gerado pelo compilador tendo a mesma assinatura da
referência de método que está implementando. A interface aproveita a contagem de referência
para seu descarte automático.

note Embora praticamente a interface usada para um método anônimo se pareça com qualquer outra interface, o
O compilador distingue entre essas interfaces especiais para que você não possa misturá-las no código.

Além dessa interface oculta, para cada invocação de um método anônimo o compilador cria um objeto oculto que contém
a implementação do método e os dados necessários para capturar o contexto da invocação. É assim que você
obtém um novo conjunto de variáveis capturadas para cada chamada do método.

Tipos de referência prontos para uso


Cada vez que você usa um método anônimo como parâmetro, você precisa definir um tipo de
dados de ponteiro de referência correspondente. Para evitar a proliferação de tipos locais, Object

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

438 - 15: métodos anônimos

Pascal fornece vários tipos de ponteiros de referência prontos para uso na unidade System.SysU-tils .

Como você pode ver no trecho de código abaixo, a maioria dessas definições de tipo usam
tipos parametrizados, de modo que com uma única declaração genérica você tem um tipo de ponteiro de
referência diferente para cada tipo de dados possível:

tipo
TProc = referência ao procedimento;
TProc<T> = referência ao procedimento(Arg1: T);
TProc<T1, T2> = referência ao procedimento(
Arg1:T1; arg2:T2);
TProc<T1, T2, T3> = referência ao procedimento(
Arg1:T1; Arg2:T2; Arg3:T3);
TProc<T1, T2, T3, T4> = referência ao procedimento(
Arg1:T1; Arg2:T2; Arg3:T3; Arg4:T4);

Usando essas declarações, você pode definir procedimentos que utilizam parâmetros de métodos
anônimos, como a seguir:

procedimento UseCode(Proc: TProc); função


DoThis (Proc: TProc): string; função DoThat(ProcInt:
TProc<Integer>): string;

No primeiro e no segundo caso você passa um método anônimo sem parâmetros, no terceiro você passa
um método com um único parâmetro Integer:

UseCode( procedimento começa


// Algum código
fim);
StrRes :=
DoThat( procedimento (I:
Inteiro) começar
// Algum código
fim);

Da mesma forma, a unidade System.SysUtils define um conjunto de tipos de métodos anônimos com um
valor de retorno genérico:

tipo
TFunc<TResult> = referência à função: TResult;
Tfunc<T, TResult> = referência à função (
Arg1: T): TResult;
TFunc<T1, T2, TResult> = referência à função (
Arg1: T1; Arg2: T2): TResultado;
TFunc<T1, T2, T3, TResult> = referência à função (
Arg1:T1; Arg2:T2; Arg3:T3):TResultado;
TFunc<T1, T2, T3, T4, TResult> = referência à função (
Arg1:T1; Arg2:T2; Arg3:T3; Arg4:T4):TResultado;
TPredicate<T> = referência à função (
Arg1: T): Booleano;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 439

Essas definições são muito amplas, pois você pode usar inúmeras combinações de tipos de dados para
até quatro parâmetros e um tipo de retorno. A última definição é muito parecida com a segunda, mas
corresponde a um caso específico e muito frequente, uma função que recebe um parâmetro genérico
e retorna um booleano.

Métodos anônimos no mundo real


À primeira vista, não é fácil compreender totalmente o poder dos métodos anônimos e os cenários que
podem se beneficiar com seu uso. É por isso que, em vez de sair com
exemplos mais complicados cobrindo o idioma, decidi me concentrar em alguns que têm
um impacto prático e fornecer pontos de partida para futuras explorações.

Manipuladores de eventos anônimos


Uma das características distintivas do Object Pascal é a implementação de manipuladores de eventos
usando ponteiros de método. Métodos anônimos podem ser usados para anexar um novo comportamento
a um evento sem precisar declarar um método separado e capturar o contexto de execução do método.
Isso evita a necessidade de adicionar campos extras a um formulário para passar parâmetros de um método
para outro.

Como exemplo (o exemplo AnonButton ), adicionei um evento de clique anônimo a um botão, declarando
um tipo de ponteiro de método adequado e adicionando um novo manipulador de eventos a uma classe
de botão personalizada (definida usando uma classe intermediária):

tipo
TAnonNotif = referência ao procedimento(Remetente: TObject);

// Interpor classe
TButton = classe(FMX.StdCtrls.TButton)
privado
FAnonClick: TAnonNotif;
procedimento SetAnonClick (valor const: TAnonNotif);
público
procedimento Clique; sobrepor;
público
propriedade AnonClick: TAnonNotif
leia FAnonClick escreva SetAnonClick;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

440 - 15: métodos anônimos

note Uma classe intermediária é uma classe derivada que tem o mesmo nome de sua classe base. Tendo duas aulas com
o mesmo nome é possível porque as duas classes estão em unidades diferentes, portanto seu nome completo
(nome-da-unidade.nomedaclasse) é diferente. Declarar uma classe interposer pode ser útil, pois você pode simplesmente
colocar um controle Button no formulário e anexar um comportamento extra a ele, sem precisar instalar um novo componente
no IDE e substitua os controles do seu formulário pelo novo tipo. O único truque que você deve lembrar é que se a
definição da classe interposer estiver em uma unidade separada (não na unidade de formulário como neste exemplo
simples), essa unidade deverá ser listada na instrução usa após a unidade que define a classe base .

O código desta classe é bastante simples, pois o método setter salva o novo ponteiro e o método Click o
chama antes de fazer o processamento padrão (ou seja, chamar o
Manipulador de eventos OnClick , se disponível):

procedimento TButton.SetAnonClick (valor const: TAnonNotif);


começar
FAnonClick := Valor;
fim;

procedimento TButton.Click;
começar
se atribuído (FAnonClick) então
FAnonClick (próprio)
herdado;
fim;

Como você pode usar esse novo manipulador de eventos? Basicamente você pode atribuir um método
anônimo a ele:

procedimento TFormAnonButton.BtnAssignClick(Sender: TObject);


começar
BtnInvoke.AnonClick :=
procedimento (Remetente: TObject)
começar
Show((Remetente como TButton).Text);
fim;
fim;

Agora, isso parece um tanto inútil, já que o mesmo efeito poderia ser facilmente alcançado usando um
método padrão de manipulador de eventos. O seguinte, em vez disso, começa a fazer a diferença, pois o
método anônimo captura uma referência ao componente que atribuiu o manipulador de eventos, referenciando
o parâmetro Sender .

Isso pode ser feito após atribuí-lo temporariamente a uma variável local, pois o parâmetro Sender do método
anônimo oculta o Sender do método BtnKeepRefClick .
parâmetro:

procedimento TFormAnonButton.BtnKeepRefClick(Sender: TObject);


era
ACompRef: TComponent;
começar
ACompRef := Remetente como TComponent;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 441

BtnInvoke.AnonClick :=
procedimento (Remetente: TObject)
começar
Show((Remetente como TButton).Text +
' '
Assinado por + ACompRef.Nome);
fim;
fim;

Ao pressionar o botão BtnInvoke , você verá sua legenda junto com o nome do componente que atribuiu
o manipulador de método anônimo.

Métodos Anônimos de Cronometragem


Os desenvolvedores frequentemente adicionam código de tempo às rotinas existentes para comparar sua
velocidade relativa. Supondo que você tenha dois fragmentos de código e queira comparar sua velocidade
executando-os alguns milhões de vezes, você poderia escrever o seguinte extraído do exemplo LargeString
do Capítulo 6:

procedimento TForm1.Button1Click(Remetente: TObject);


era
Str1, Str2: sequência;
Eu: Inteiro;
T1: Tcronômetro;
começar
'Marco '
Str1 :=
';
Str2 := 'Eu canto ;

T1 := TStopwatch.StartNew;
para I := 1 para MaxLoop faça
Str1 := Str1 + Str2;
T1.Parada; '
'Comprimento: + Str1.Length.ToString);
'
'Concatenação: + T1.ElapsedMilliseconds.ToString);
Mostrar(Mostrar(fim;

Um segundo método possui código semelhante, mas usou a classe TStringBuilder em vez de concatenação
simples.

Agora podemos aproveitar os métodos anônimos para criar um esqueleto de temporização e passar o
código específico como parâmetro, como fiz em uma versão atualizada do código, no exemplo
AnonLargeStrings . Em vez de repetir o código de temporização indefinidamente, você pode escrever uma
função com o código de temporização que invocaria o trecho de código por meio de um método
anônimo sem parâmetros:

função TimeCode (NLoops: Inteiro; Proc: TProc): string;


era
T1: Tcronômetro;
Eu: Inteiro;
começar
T1 := TStopwatch.StartNew;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

442 - 15: métodos anônimos

para I := 1 para NLoops fazer


Proc;
T1.Parada;
Resultado:= T1.ElapsedMilliseconds.ToString;
fim;

procedimento TForm1.Button1Click(Remetente: TObject);


era
Str1, Str2: sequência;
começar
'
Str1 := 'Marco ;
'
Str2 := 'Eu canto ;
'
Mostrar( 'Concatenação: +
TimeCode(MaxLoop,
procedimento()
começar
Str1 := Str1 + Str2;
fim));
'
'Comprimento: + Str1.Length.ToString);
Mostrar(fim;

Observe, porém, que se você executar a versão padrão e aquela baseada em métodos anônimos,
obterá uma saída ligeiramente diferente, com a versão do método anônimo sofrendo uma
penalidade de aproximadamente 10%.

A razão é que, em vez de executar diretamente o código local, o programa precisa fazer uma
chamada virtual para a implementação do método anônimo. Como essa diferença é consistente, o
código de teste faz todo o sentido, desde que os resultados dos testes das duas implementações não
sejam usados de forma intercambiável.

No entanto, se você precisar extrair desempenho do seu código, usar métodos anônimos não será tão
rápido quanto escrever o código diretamente usando uma função direta. Usar um ponteiro de método
não virtual provavelmente estaria em algum lugar entre os dois em termos de desempenho.

Sincronização de threads
Em aplicativos multithread que precisam atualizar a interface do usuário, você não pode acessar
propriedades de componentes visuais (ou objetos na memória) que fazem parte do thread principal
sem um mecanismo de sincronização. As bibliotecas de componentes visuais do Delphi não são
inerentemente seguras para threads (como acontece com a maioria das bibliotecas de interface de
usuário), o que significa que, se duas ou mais threads estiverem acessando um objeto ao mesmo tempo, o estado do objeto
poderia ser comprometido.

A solução clássica oferecida pela classe TThread em Object Pascal é chamar um método especial,
Synchronize, passando como parâmetro a referência a outro método, aquele a ser executado com
segurança. Este segundo método não pode ter parâmetros, portanto é prática comum adicionar
campos extras à classe de thread para passar as informações de um

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 443

método para outro. Como exemplo prático, no livro Mastering Delphi 2005 escrevi uma aplicação WebFind
(programa que executa pesquisas no Google via HTTP e extrai os links resultantes do HTML da página),
com a seguinte classe de thread:

tipo

TFindWebThread = classe(TThread)
protegido
FAddr, FText, FStatus: string;
procedimento Executar; sobrepor;
procedimento AddToList;
procedimento ShowStatus;
procedimento GrabHtml;
procedimento HtmlToList;
procedimento HttpWork(Remetente: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
público
FStrUrl: string;
FStrRead: string;
fim;

Os três campos de string protegidos e alguns dos métodos extras foram introduzidos para suportar a
sincronização com a interface do usuário. Por exemplo, o manipulador de eventos HttpWork conectado ao
evento OnReceiveData de um componente THTTPClient interno (uma parte do componente da biblioteca de
cliente HTTP Delphi), costumava ter o seguinte código, que chamava o método ShowStatus :

procedimento TFindWebThread.HttpWork(const Sender: TObject;


AContentLength, AReadCount: Int64; var AAbort: Booleano);
começar
' ' '
Estado F := 'Recebido + IntToStr(AReadCount) + para +FStrUrl;
Sincronizar(ShowStatus);
fim;

procedimento TFindWebThread.ShowStatus;
começar
Form1.StatusBar1.SimpleText := FStatus;
fim;

O método Synchronize do Object Pascal RTL tem duas definições sobrecarregadas diferentes:

tipo
TThreadMethod = procedimento do objeto;
TThreadProcedure = referência ao procedimento;

TThread = classe
procedimento Sincronizar(AMethod: TThreadMethod); sobrecarga;
procedimento Sincronizar(AThreadProc: TThreadProcedure); sobrecarga;

Usando a versão com um parâmetro de método anônimo, podemos remover o FStatus


campo de texto e a função ShowStatus da classe de formulário e reescreva o HttpWork

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

444 - 15: métodos anônimos

manipulador de eventos usando a nova versão do Synchronize e um método anônimo como


parâmetro:

procedimento TFindWebThreadAnon.HttpWork(const Sender: TObject;


AContentLength, AReadCount: Int64; var AAbort: Booleano);
começar
Sincronizar(
procedimento
começar
Form1.StatusBar1.SimpleText :=
'
'Recebido por + IntToStr(AReadCount) +
' '
+FStrUrl;
fim);
fim;

Usando a mesma abordagem em todo o código da classe, a classe de thread se torna a seguinte
(você pode encontrar ambas as classes de thread na versão do exemplo WebFind
que acompanha o código-fonte deste livro):

tipo
TFindWebThreadAnon = classe(TThread)
protegido
procedimento Executar; sobrepor;
procedimento GrabHtml;
procedimento HtmlToList;
procedimento HttpWork(const Sender: TObject; AContentLength: Int64;
AReadCount: Int64; var AAbort: Booleano);
público
FStrUrl: string;
FStrRead: string;
fim;

O uso de métodos anônimos simplifica o código necessário para a sincronização de threads, pois
você pode evitar campos temporários.

note Os métodos anônimos têm muitos relacionamentos com threading, porque um thread é usado para executar algum código
e o método anônimo representa o código. É por isso que há suporte na classe TThread para utilizá-los, mas também
na Biblioteca de Programação Paralela (em TParallel.For e para definir um TTask). Dado que o exame do multithreading
vai muito além deste capítulo, não adicionarei mais exemplos nessa direção. Ainda assim, usarei outro thread no
próximo exemplo, porque isso geralmente é um requisito ao fazer uma chamada HTTP.

AJAX em objeto Pascal


O último exemplo desta seção, a demonstração do aplicativo AnonAjax , é um dos meus exemplos
favoritos de métodos anônimos (mesmo que um pouco extremo). A razão é que aprendi a usar
encerramentos (ou métodos anônimos) em JavaScript enquanto escrevia aplicativos AJAX com a
biblioteca jQuery há alguns anos.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 445

note A sigla AJAX significa Asynchronous JavaScript XML, pois este era originalmente o formato usado nas chamadas de
serviços web pelo navegador. À medida que esta tecnologia se tornou mais popular e difundida, e os serviços web
migraram para a arquitetura REST e o formato JSON, o termo AJAX desapareceu em
favor de REST. Decidi manter esse nome antigo para a demonstração de qualquer maneira, já que ele explica
o propósito do aplicativo e a história por trás dele. Você pode ler mais
em: https://en.wikipedia.org/wiki/Ajax_(programming).

A função global AjaxCall gera um thread, passando para o thread um método anônimo para
executar na conclusão. A função é apenas um wrapper em torno do thread
construtor:

tipo
TAjaxCallback = referência ao procedimento(
ResponseContent: TStringStream);

procedimento AjaxCall(const StrUrl: string;


AjaxCallback:TAjaxCallback);
começar
TAjaxThread.Create(StrUrl, AjaxCallback);
fim;

Todo o código está na classe TAjaxThread , uma classe de thread com um componente cliente
HTTP interno (da biblioteca Cliente HTTP) usado para acessar uma URL de forma assíncrona:

tipo
TAjaxThread = classe(TThread)
privado
FHttp: THTTPCliente;
FUrl: string;
FAjaxCallback: TAjaxCallback;
protegido
procedimento Executar; sobrepor;
público
construtor Create(const StrUrl: string;
AjaxCallback:TAjaxCallback);
destruidor Destruir; sobrepor;
fim;

O construtor faz alguma inicialização, copiando seus parâmetros para os campos locais
correspondentes da classe thread e criando o objeto FHttp . A verdadeira essência da classe está
em seu método Execute , que faz a solicitação HTTP, salvando o resultado em um fluxo que é
posteriormente redefinido e passado para a função de retorno de chamada – o método anônimo:

procedimento TAjaxThread.Execute;
era
AResponseContent: TStringStream;
começar
AResponseContent := TStringStream.Create;
tentar
FHttp.Get(FUrl, AResponseContent);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

446 - 15: métodos anônimos

AResponseContent.Position := 0;
FAjaxCallback(AResponseContent);
finalmente
AResponseContent.Free;
fim;
fim;

Como exemplo de uso, o exemplo AnonAjax possui um botão para copiar o conteúdo de uma página
web para um controle Memo (após adicionar a URL solicitada):

procedimento TFormAnonAjax.BtnReadClick(Sender: TObject);


começar
AjaxCall(EdUrl.Text,
procedimento (AResponseContent: TStringStream)
começar
Memo1.Lines.Text := AResponseContent.DataString;
'
'Da URL:
Memo1.Lines.Insert(0, + EdUrl.Text);
fim);
fim;

Após a conclusão da solicitação HTTP, você poderá realizar qualquer processamento nela. Um
exemplo seria extrair links do HTML (de uma forma semelhante à aplicação WebFind abordada
anteriormente). Novamente, para tornar esta função flexível, ela toma como parâmetro o método
anônimo a ser executado para cada link:

tipo
TLinkCallback = referência ao procedimento(const StrLink: string);

procedimento ExtractLinks(StrData: string; ProcLink: TLinkCallback);


era
StrAddr: string;
NBegin, NEend: Inteiro;
começar
StrData := LowerCase(StrData);
NDo := 1;
repita
NBegin := PosEx(se 'href="http' , StrData, NBegin);
NBegin <> 0 então
começar
// Encontre o fim do Referência HTTP
NDo := NDo + 6; '"'
NEEnd := PosEx( , StrData, NBegin);
StrAddr := Copiar(StrData, NBegin, NEnd - NBegin);
// Mover sobre
NBegin := NEEnd + 1;
// Executar método anônimo
ProcLink(StrAddr)
fim;
até NBegin = 0;
fim;

Se você aplicar esta função ao resultado de uma chamada AJAX e fornecer um método adicional para
processamento, você acaba com duas chamadas de método anônimas aninhadas, como no
segundo botão do exemplo AnonAjax :

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

15: métodos anônimos - 447

procedimento TFormAnonAjax.BtnLinksClick(Sender: TObject);


começar
AjaxCall(EdUrl.Text,
procedimento (AResponseContent: TStringStream)
começar
ExtractLinks(AResponseContent.DataString,
procedimento (const AUrl: string)
começar
' em '
Memo1.Lines.Add(AUrl + fim); + EdUrl.Text);

fim);
fim;

Neste caso o controle Memo receberá uma coleção de links, ao invés do HTML da página
retornada. Uma variação da rotina de extração de link acima seria uma rotina de extração
de imagem. A função ExtractImages captura a fonte (src) das tags img do arquivo HTML
retornado e chama outro método anônimo compatível com TLinkCallback . Agora você
pode imaginar a abertura de uma página HTML (com a função AjaxCall ), extrair os links da
imagem e usar o AjaxCall novamente para obter as imagens reais. Isso significa usar um
fechamento triplo aninhado, em uma estrutura de codificação que alguns programadores
de Object Pascal podem achar muito incomum, mas que é certamente muito poderosa e expressiva:

procedimento TFormAnonAjax.BtnImagesClick(Sender: TObject);


era
NHit: Inteiro;
começar
NHit := 0;
AjaxCall(EdUrl.Text,
procedimento (AResponseContent: TStringStream)
começar
ExtractImages(AResponseContent.DataString,
procedimento (const AUrl: string)
começar
Inc(NHit);
Memo1.Lines.Add(IntToStr(NHit) + AUrl + + '.' +
'
EdUrl.Text); em
'
se NHit = 1 então // Carregue o primeiro
comece
var RealURL := IfThen(AURL[1]='/',
EdUrl.Text + AURL, AURL); // Expandir URL
AjaxCall(RealUrl,
procedimento (AResponseContent: TStringStream)
começar
Imagem1.Imagem.Gráfico.
LoadFromStream(AResponseContent);
fim);
fim;
fim);
fim);
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

448 - 15: métodos anônimos

note Este trecho de código foi tema de um post meu no blog, “Anonymous, Anonymous, Anonymous” de setembro de 2008, que atraiu alguns
comentários, como você pode ver em:
https://blog.marcocantu.com/blog/anonymous_3.html.

Além do gráfico só funcionar no caso de você estar carregando um arquivo com o mesmo formato
daquele que já está no componente Imagem, o código e seu resultado são impressionantes.
Observe em particular a sequência de numeração, baseada na captura da variável local NHit . O
que acontece se você pressionar o botão duas vezes, em uma sequência rápida?
Cada um dos métodos anônimos obterá uma cópia diferente do contador NHit e poderá
potencialmente ser exibido fora de sequência na lista, com o segundo thread começando a
produzir sua saída antes do primeiro.
Apesar deste problema potencial, o resultado do uso deste último botão é exibir uma imagem
como você pode ver na Figura 15.1.

Figura 15.1: A saída da


chamada de método
anônimo aninhado triplo
para recuperar uma imagem de

uma web
página

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 449

16: reflexão e
atributos

Tradicionalmente, os compiladores de linguagens fortemente tipadas estaticamente, como Pascal,


forneciam pouca ou nenhuma informação sobre os tipos disponíveis em tempo de execução. Todas as
informações sobre os tipos de dados ficaram visíveis apenas durante a fase de compilação.

A primeira versão do Object Pascal rompeu com essa tradição, ao fornecer informações de tempo de execução
para propriedades e outros membros da classe marcados com uma diretiva específica do compilador,
publicada. Este recurso foi habilitado para classes compiladas com uma configuração específica {$M+} e é a
base do mecanismo de streaming por trás dos arquivos DFM da VCL (e dos arquivos FMX da biblioteca
FireMonkey) e da forma como você trabalha com o formulário
e outros designers visuais.

Quando foi disponibilizado pela primeira vez no Delphi 1, esse recurso era uma ideia completamente nova,
que mais tarde outras ferramentas de desenvolvimento adotaram, ampliaram e desenvolveram.

Primeiro, havia extensões para o sistema de tipos (disponível apenas em Object Pascal) para dar conta
da descoberta de métodos e da invocação dinâmica em COM. Isso ainda é suportado no Object Pascal por ID
de despacho, aplicação de métodos a variantes e outros recursos relacionados ao COM. Eventualmente, o
suporte COM no Object Pascal foi estendido com seu próprio tipo de informação de tipo de tempo de execução,
mas este é um tópico muito além do escopo de um livro de linguagem.

O advento de ambientes gerenciados, como Java e .NET, trouxe uma forma muito extensa de informações
sobre o tipo de tempo de execução, com RTTI detalhado vinculado ao compilador

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

450 - 16: reflexão e atributos

aos módulos executáveis e disponíveis para descoberta por programas que usam esses módulos. Isto
tem a desvantagem de desvendar algumas das partes internas do programa e de aumentar o
tamanho dos módulos, mas traz consigo novos modelos de programação que combinam um pouco da
flexibilidade das linguagens dinâmicas com a estrutura sólida e a velocidade das linguagens fortemente
tipadas.

Quer você goste ou não (e isso de fato foi objeto de intenso debate na época em que esse recurso foi
introduzido), o Object Pascal está se movendo lentamente na mesma direção,
e a adopção de uma forma extensiva de RTTI marca um passo muito significativo nessa direcção. Como
veremos, você pode cancelar o RTTI, mas se não o fizer, poderá aproveitar algum poder extra em seus
aplicativos.

O assunto está longe de ser simples, então prosseguirei em etapas.

· Primeiro nos concentraremos no novo RTTI estendido que está embutido no compilador e no
novas classes da unidade Rtti que você pode usar para explorá-la.

· Em segundo lugar, examinarei a nova estrutura e invocação dinâmica do TValue .

· Terceiro, apresentarei atributos personalizados, um recurso paralelo ao seu equivalente .NET e que permite
estender as informações RTTI geradas pelo compilador.

Apenas na última parte do capítulo tentarei voltar às razões por detrás do RTTI alargado e examinar
exemplos práticos da sua utilização.

RTTI estendido
O compilador Object Pascal gera por padrão muitas informações estendidas de RTTI.
Essas informações de tempo de execução incluem todos os tipos, incluindo classes e todos os
outros tipos definidos pelo usuário, bem como os principais tipos de dados predefinidos pelo compilador e
abrangem campos publicados, bem como os públicos, até mesmo elementos protegidos e privados. Isso é necessário
ser capaz de mergulhar na estrutura interna de qualquer objeto.

Um primeiro exemplo
Antes de examinarmos as informações geradas pelo compilador e as diversas técnicas para acessá-las,
deixe-me chegar à conclusão e mostrar o que pode ser feito usando o RTTI. O exemplo específico é mínimo
e poderia ter sido escrito com o RTTI antigo, mas deve dar uma ideia do que estou falando (considerando
também que nem todos os desenvolvedores de Object Pascal usaram explicitamente o RTTI tradicional).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 451

Suponha que você tenha um formulário com um botão, como no exemplo RttiIntro . Você pode escrever o
seguinte código para ler o valor da propriedade Text do controle :

usa
Rtti;

procedimento TFormRttiIntro.BtnInfoClick(Sender: TObject);


era
Contexto: TRttiContext;
começar
Mostrar(Contexto.
GetType(TButton).
'Texto'
ObterPropriedade().
GetValue(Remetente).ToString);
fim;

O código usa o registro TRttiContext para se referir a informações sobre o tipo TButton , desde essas informações
de tipo até os dados RTTI sobre uma propriedade, e esses dados de propriedade são usados para se referir ao
valor real da propriedade, que é convertido em uma string.

Se você está se perguntando como isso funciona, continue lendo. O que quero dizer aqui é que essa abordagem
agora pode ser usada não apenas para acessar uma propriedade dinamicamente, mas também para ler os
valores dos campos, incluindo campos privados.

Também podemos alterar o valor de uma propriedade, conforme o segundo botão do RttiIntro
exemplo mostra:

procedimento TFormRttiIntro.BtnChangeClick(Sender: TObject);


era
Contexto: TRttiContext;
AProp: TRttiProperty;
começar
AProp := Context.GetType(TButton).GetProperty('Texto');
'*' ,
Aprop.SetValue(BtnChange, StringOfChar(Random (10) + 1));
fim;

Este código substitui o Texto por um número aleatório de *s. A diferença do código
acima é que possui uma variável local temporária referente às informações do RTTI da propriedade. Agora que
você tem uma ideia do que estamos fazendo, vamos começar do início verificando as informações estendidas do
RTTI geradas pelo compilador.

Informações geradas pelo compilador


Não há nada que você precise fazer para permitir que o compilador adicione essas informações extras ao seu
programa executável (seja qual for o tipo: aplicativo, biblioteca, pacote e outros). Basta abrir um projeto e compilá-
lo. Por padrão, o compilador gera RTTI estendido para todos os campos (incluindo os privados) e para métodos e
propriedades públicas e publicadas.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

452 - 16: reflexão e atributos

Você pode se surpreender com o fato de obter informações de RTTI para campos privados, mas isso é
necessário para operações dinâmicas, como serialização de objetos binários e rastreamento de objetos
no heap.

Você pode controlar a geração estendida de RTTI de acordo com uma matriz de configurações: Em um
eixo você tem a visibilidade e no outro o tipo de membro. A tabela a seguir descreve o padrão do sistema:

Campo Método Propriedade


Privado x
Protegido x
Público x x x
Publicados x x x

Tecnicamente, as quatro configurações de visibilidade são indicadas usando o seguinte tipo de conjunto,
declarado na unidade do sistema :

tipo
TVisibilityClasses = conjunto de (vcPrivate,
vcProtegido, vcPublic, vcpublicado);

Existem alguns valores constantes prontos para uso para este conjunto, indicando as configurações
padrão de visibilidade RTTI aplicadas ao TObject e herdadas por todas as outras classes por padrão:
const
DefaultMethodRttiVisibility = [vcPublic, vcPublished];
DefaultFieldRttiVisibility = [vcPrivate..vcPublished];
DefaultPropertyRttiVisibility = [vcPublic, vcPublished];

A informação produzida pelo compilador é controlada por uma nova diretiva, $RTTI, que possui um
status que indica se a configuração é para o tipo determinado ou também para seus descendentes
(EXPLICIT ou INHERITED) seguido de três especificadores para definir a visibilidade para métodos,
campos e propriedades.

O padrão aplicado na unidade do Sistema é:

{$RTTI HERANÇA

MÉTODOS(DefaultMethodRttiVisibility) FIELDS(DefaultFieldRttiVisibility) PROPERTIES(DefaultPropertyRttiVisibility)}

Para desabilitar completamente a geração de RTTI estendido para todos os membros de suas classes você
pode usar a seguinte diretiva:

{$RTTI MÉTODOS EXPLÍCITOS([]) CAMPOS([]) PROPRIEDADES([])}

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 453

note Você não pode colocar a diretiva RTTI antes da declaração da unidade, como acontece com outros compiladores
diretivas, porque depende das configurações definidas na unidade do sistema . Se fizer isso, você receberá uma mensagem de erro interna, que
não é particularmente intuitiva. Em qualquer caso, basta mantê-lo após a declaração da unidade.

Ao usar esta configuração, considere que ela será aplicada apenas ao seu código e que uma
remoção completa não é possível, pois as informações do RTTI para o RTL e outras classes da
biblioteca já estão compiladas nos DCUs e pacotes correspondentes.

Tenha também em mente que a diretiva $RTTI não causa nenhuma alteração no RTTI tradicional gerado para
tipos publicados: este ainda é produzido independentemente da diretiva $RTTI .

note As classes de processamento RTTI, disponíveis na unidade System.Rtti e abordadas na próxima seção,
conecte-se ao RTTI tradicional e sua estrutura PTypeInfo .

O que você pode fazer com esta diretiva é impedir que o RTTI estendido seja gerado para suas
próprias classes. No extremo oposto da escala, você também pode aumentar a quantidade de RTTI
gerada, incluindo métodos e propriedades privadas e protegidas, se desejar (embora isso não faça
muito sentido).
O efeito óbvio de adicionar informações estendidas de RTTI a um arquivo executável é que o
o arquivo ficará maior (o que tem a principal desvantagem de um arquivo maior para distribuir, já
que o tempo extra de carregamento e o consumo de memória não são tão relevantes). Dessa forma,
se você decidir não usar RTTI em seu código, poderá remover o RTTI das unidades do seu
programa e isso poderá ter um efeito positivo. Entretanto, o RTTI é uma técnica poderosa, como
você verá neste capítulo, e na maioria dos casos vale a pena o tamanho extra do executável.

Vinculação de tipo fraco e forte


O que mais você poderia fazer para reduzir o tamanho do programa? Na verdade, há algo que você
pode fazer, mesmo que o efeito não seja grande, será perceptível.

Ao avaliar as informações de RTTI disponíveis no arquivo executável, considere que o que o


compilador adiciona, o vinculador pode remover. Por padrão, classes e métodos não compilados no
programa não receberão o RTTI estendido (o que seria bastante inútil), pois também não recebem
o RTTI básico.
No extremo oposto da escala, se você deseja que todos os RTTI estendidos sejam incluídos e
funcionem, você precisa vincular classes e métodos pares aos quais não se refere explicitamente
em seu código.

Existem duas diretivas de compilador que você pode usar para controlar as informações vinculadas
ao executável. A primeira, que está totalmente documentada, é a direção $WeakLinkRTTI.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

454 - 16: reflexão e atributos

positivo. Ao ativá-lo, para tipos não utilizados no programa, tanto o tipo em si quanto suas informações
de RTTI serão removidos do executável final.

Alternativamente, você pode forçar a inclusão de todos os tipos e seu RTTI estendido usando o comando
Diretiva $StrongLinkTypes . O efeito em muitos programas é dramático, com um aumento de quase
duas vezes no tamanho do programa.

A Unidade RTTI
Se a geração de RTTI estendido para todos os tipos é o primeiro pilar para a reflexão que será abordada
em breve no Object Pascal, o segundo pilar é a capacidade de navegar por essas informações
de maneira fácil e de alto nível, graças ao Unidade System.Rtti . O terceiro pilar, como veremos mais
adiante, é o suporte a atributos customizados. Mas deixe-me prosseguir um passo de cada vez.

Tradicionalmente, os aplicativos Object Pascal poderiam (e ainda podem) usar as funções da unidade
System.TypInfo para acessar as informações de tipo de tempo de execução publicadas . Esta
unidade define diversas estruturas e funções de dados de baixo nível (todas baseadas em ponteiros
e registros) com algumas rotinas de nível superior para tornar as coisas um pouco mais fáceis.

A unidade Rtti , por outro lado, facilita muito o trabalho com o RTTI estendido, fornecendo um conjunto
de classes com métodos e propriedades adequadas. Para acessar os diversos objetos, o ponto de
entrada é a estrutura de registros TRttiContext , que possui quatro métodos para procurar os tipos
disponíveis:

função GetType (ATypeInfo: Ponteiro): TRttiType; sobrecarga;


função GetType (AClass: TClass): TRttiType; sobrecarga;
função GetTypes: TArray<TRttiType>;
função FindType (const AQualifiedName: string): TRttiType;

Como você pode ver você pode passar uma classe, um ponteiro PTypeInfo obtido de um tipo, um nome
qualificado (o nome do tipo decorado com o nome da unidade, como em “System.
TObject”), ou recuperar a lista completa de tipos, definida como uma matriz de tipos RTTI, ou mais
precisamente como TArray<TRttiType>.

Esta última chamada é a que usei na listagem a seguir, uma versão simplificada do código no exemplo
TypesList :

procedimento TFormTypesList.BtnTypesListClick(Sender: TObject);


era
AContext: TRttiContext;
OsTipos: TArray<TRttiType>;
AType: TRttiType;
começar
TheTypes := AContext.GetTypes;
para AType em TheTypes faça

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 455

se AType.IsInstance então
Show(AType.QualifiedName);
fim;

O método GetTypes retorna a lista completa de tipos de dados, mas o programa filtra apenas os
tipos que representam classes. Existem cerca de uma dúzia de outras classes que representam tipos
na unidade.

note A unidade Rtti refere-se aos tipos de classe como “instâncias” e “tipos de instância” (como em TRttiInstanceType).
Isso é um pouco confuso, pois geralmente usamos os termos instância para nos referirmos a um objeto real.

Os objetos individuais na lista de tipos são de classes que herdam da classe base TRttiType .
Especificamente, podemos procurar o tipo de classe TRttiInstanceType , reescrevendo o código acima
como no seguinte trecho modificado:

para AType em TheTypes faça se


AType for TRttiInstanceType então
Show(AType.QualifiedName);

O código real da demonstração é um pouco mais complexo, pois preenche primeiro uma lista de
strings, classifica os elementos e, em seguida, preenche um controle ListView, usando BeginUdpate e
EndUdpate para otimização, adiando uma repintura do controle até o final da operação (usando um bloco try-
finally para garantir que a operação final seja sempre executada):

era
AContext: TRttiContext; OsTipos:
TArray<TRttiType>; SList: TStringList;
AType: TRttiType;
STypeName: string;
comece ListView1.ClearItems;
SList :=
TStringList.Create; tente
TheTypes := AContext.GetTypes; para

AType em TheTypes faça

se AType.IsInstance então
SList.Add(AType.QualifiedName); SList.Sort;

ListView1.BeginUpdate; tente

STypeName em SList do
(ListView1.Items.Add).Text := STypeName; finalmente

ListView1.EndUpdate; fim;

finalmente
SList.Free; fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

456 - 16: reflexão e atributos

fim;

Este código produz uma lista bastante longa com centenas de tipos de dados, com o
número real dependendo da plataforma e da versão do compilador, como você pode ver na Figura
16.1. Observe que a imagem lista os tipos da unidade RTTI, abordados na próxima seção.

Figura 16.1:
A saída do
exemplo TypesList

As aulas RTTI na Unidade Rtti


Na lista a seguir, você pode ver todo o gráfico de herança para as classes que derivam da
classe abstrata TRttiObject e são definidas na unidade System.Rtti :

TRttiObject // Abstrato
TRttiNamedObject
Tipo TRtti
TRttiStructuredType // Abstrato
TRttiRecordType
TRttiInstanceType
TRttiInterfaceType
TRttiOrdinalType
TRttiEnumerationType
TRttiInt64Type
TRttiInvokableType
TRttiMethodType

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 457

TRttiProcedureType
TRttiClassRefType
TRttiEnumerationType
TRttiSetType
TRttiStringType
TRttiAnsiStringType
TRttiFloatType
TRttiArrayType
TRttiDynamicArrayType
TRttiPointerType
TRttiMembro
TRttiField
Propriedade TRtti
Propriedade TRttiInstance
Propriedade TRttiIndexed
Método TRtti
Parâmetro TRtti
Pacote TRtti
TRttiManagedField

Cada uma dessas classes fornece informações específicas sobre o tipo nomeado. Por exemplo, apenas
um TRttiInterfaceType oferece uma maneira de acessar o GUID da interface.

note Na primeira implementação da unidade Rtti não havia objeto RTTI para acessar propriedades indexadas
(como Strings[] de um TStringList). Isso foi adicionado posteriormente e agora está disponível, tornando as informações do
tipo de tempo de execução realmente completas.

Gerenciamento de vida útil de objetos RTTI e o


Registro TRttiContext

Se você observar o código-fonte do método BtnTypesListClick listado anteriormente, há algo que parece
muito errado. A chamada GetTypes retorna uma matriz de tipos, mas o código não libera esses objetos
internos.
A razão é que a estrutura de registro TRttiContext se torna a proprietária efetiva de todos os objetos
RTTI que estão sendo criados. Quando o registro é descartado (isto é, quando sai do escopo), uma
interface interna é limpa invocando seu próprio destruidor que libera todos os objetos RTTI que foram
criados através dele.

O registro TRttiContext , na verdade, tem uma função dupla. Por um lado, controla a vida útil de
os objetos RTTI (como acabei de explicar), por outro lado, armazena em cache informações RTTI que
são bastante caras para recriar com uma pesquisa. É por isso que você pode querer manter as referências
ao registro TRttiContext ativas por um longo período, permitindo continuar acessando os objetos RTTI
que ele possui sem ter que recriá-los, que é a parte demorada que você deseja evitar.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

458 - 16: reflexão e atributos

Internamente o registro TRttiContext utiliza um pool global do tipo TRttiPool, que utiliza uma seção crítica
para sincronizar a execução do código entre diferentes threads, tornando sua thread de acesso
segura.

note Há exceções à segurança de thread do mecanismo de pooling RTTI, descritas com alguns detalhes nos comentários disponíveis
na própria unidade Rtti .

Portanto, para ser mais preciso, o pool RTTI é compartilhado entre os registros TRttiContext , de modo
que os objetos RTTI agrupados são mantidos enquanto pelo menos um registro TRttiContext estiver na
memória. Para citar o comentário na unidade:

{... a trabalhando com objetos RTTI sem erros. Mantendo-se em pelo pelo menos um contexto estar vivo mantém o
um menos um contexto vivo deve Piscina
variável é válida}

Em outras palavras, você deve evitar armazenar em cache e manter objetos RTTI depois de liberar
o contexto RTTI. Este é um exemplo que leva a uma violação de acesso à memória (novamente parte do
exemplo TypesList ):

função GetThisType (AClass: TClass): TRttiType;


era
AContext: TRttiContext;
começar
Resultado:= AContext.GetType(AClass);
fim;

procedimento TFormTypesList.Button1Click(Sender: TObject);


era
AType: TRttiType;
começar
AType := GetThisType(TForm);
Show(AType.QualifiedName);
fim;

Resumindo, os objetos RTTI são gerenciados pelo contexto e você não deve mantê-los por perto. O
contexto por sua vez é um registro, portanto é descartado automaticamente. Você poderá ver o código
que usa o TRttiContext da seguinte maneira:

AContext := TRttiContext.Create;
tentar
// Usar o contexto
finalmente
AContext.Free;
fim;

O pseudoconstrutor e o pseudodestruidor definem a interface interna, que gerencia


as estruturas de dados reais usadas nos bastidores, sem limpar o mecanismo de pooling. Entretanto, como
esta operação é automática para um tipo local, como um registro, isso não é necessário, a menos que em
algum lugar você faça referência ao registro de contexto usando um ponteiro.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 459

Exibindo informações da turma


Os tipos mais relevantes que você pode querer inspecionar em tempo de execução são
certamente os chamados tipos estruturados, ou seja, instâncias, interfaces e registros.
Focando nas instâncias, podemos nos referir ao relacionamento entre classes, seguindo o BaseType
informações disponíveis para tipos de instância.
Acessar tipos é certamente um ponto de partida interessante, mas o que é relevante e
especificamente novo é a capacidade de aprender mais detalhes desses tipos, incluindo seus
membros. Ao clicar em um dos tipos o programa exibe uma lista de propriedades, métodos e
campos do tipo, em três páginas de um controle aba, como você pode ver na Figura 16.2.

Figura 16.2: As
informações detalhadas
do tipo exibidas pelo exemplo
TypesList para a
classe TRttiMethod

A unidade desta forma secundária, que provavelmente pode ser adaptada e expandida para ser
usada como um navegador de tipo genérico em outras aplicações, possui um método chamado
ShowTypeIn-formation que percorre cada propriedade, método e campo de um determinado
tipo, adicionando-os a três caixas de listagem separadas com a indicação de sua visibilidade (pri
para privado, pro para protegido, pub para público e pbl para publicado, conforme retornado por uma
instrução case simples na função VisibilityToken ):

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

460 - 16: reflexão e atributos

procedimento TFormTypeInfo.ShowTypeDetails(TypeName: string);


era
AContext: TRttiContext;
AType: TRttiType;
Propriedade A: TRttiProperty;
Método AM: Método TRtti;
Campo AF: TRttiField;
começar
AType := AContext.FindType(TypeName);
se não for atribuído (AType), então
Saída;

LabelType.Text := AType.QualifiedName;
para AProperty em Atype.GetProperties faça
FormTypeInfo.LVProperties.Items.Add.Text := AProperty.Name +
' '
':' + AProperty.PropertyType.Name + +
VisibilityToken(AProperty.Visibility);
para AMethod em Atype.GetMethods faça ' '
LVMethods.Items.Add.Text := AMethod.Name + +
VisibilityToken(AMethod.Visibility);
para AField em AType.GetFields faça
LVFields.Items.Add.Text := AField.Name + ':' +
' '
AField.FieldType.Name + +
VisibilityToken(AField.Visibility);
fim;

Você pode extrair mais informações dos tipos dessas propriedades, obter listas de parâmetros dos métodos
e verificar o tipo de retorno e muito mais. Aqui não quero construir um navegador RTTI completo, mas
apenas dar uma ideia do que pode ser alcançado.

RTTI para pacotes


Além dos métodos que você pode utilizar para acessar um tipo ou a lista de tipos, o registro TRtti-
Context possui outro método muito interessante, GetPackages, que retorna uma lista dos pacotes de
tempo de execução utilizados pela aplicação atual. Se você executar esse método em um aplicativo
compilado sem pacotes de tempo de execução, tudo o que você obterá será o próprio arquivo executável.
Mas se você executá-lo em um aplicativo compilado com pacotes de tempo de execução, você obterá
uma lista desses pacotes. A partir daí, você pode se aprofundar nos tipos disponibilizados por cada
dos pacotes. Observe que neste caso a lista de tipos é muito mais longa, pois os tipos RTL e de biblioteca
visual não utilizados pela aplicação não são removidos pelo vinculador inteligente.

Se você usar pacotes de tempo de execução, também poderá recuperar a lista de tipos de cada um dos
pacotes (e o próprio arquivo executável), usando código como:

era
AContext: TRttiContext;
Pacote A: Pacote TRtti;
AType: TRttiType;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 461

começar
para APackage em AContext.GetPackages faça
começar
'PACOTE '
ListBox1.Items.Add(+ APackage.Nome);
para AType em APackage.GetTypes faça
se AType.IsInstance então
começar
// mostra o nome do tipo com espaços para recuo
' - '
ListBox1.Items.Add(end; + AType.QualifiedName);

fim;

note Pacotes em Object Pascal podem ser usados para adicionar componentes ao ambiente de desenvolvimento, como vimos no
Capítulo 11. Entretanto, pacotes também podem ser usados em tempo de execução, implantando um executável principal
com alguns pacotes de tempo de execução, em vez de um único, arquivo executável maior. Se você está familiarizado com
o desenvolvimento do Windows, os pacotes têm uma função semelhante às DLLs (e tecnicamente são DLLs), ou ainda
mais precisamente como assemblies .NET. Embora os pacotes desempenhem um papel muito importante no
Windows, eles não são atualmente suportados em plataformas móveis (também devido a limitações de implantação de
aplicativos do sistema operacional, como no iOS).

A Estrutura TValue
O novo RTTI estendido não apenas permite navegar pela estrutura interna de um programa, mas
também fornece informações específicas, incluindo valores de propriedades e campos. Enquanto a unidade
TypInfo fornece a função GetPropValue para acessar uma propriedade genérica e recuperar um tipo
de variante com seu valor, a nova unidade Rtti usa uma estrutura diferente para armazenar um elemento
não digitado, o registro TValue .

O registro TValue pode armazenar quase qualquer tipo de dados Object Pascal possível e faz isso por
manter o controle da representação de dados original, mantendo os dados e seu tipo de dados. O que
ele pode fazer é ler e gravar dados no formato determinado. Se você escrever um número inteiro em
TValue, só poderá ler um número inteiro dele. Se você escrever uma string, poderá lê-la.

O que não pode fazer é converter de um formato para outro. Portanto, mesmo que um TValue tenha
um método AsString e AsInteger , você poderá usar o primeiro apenas se o dado que representa for de
fato uma string, o segundo apenas se você originalmente atribuiu um número inteiro a ele. Para
Por exemplo, neste caso você pode usar o método AsInteger e, se chamar o método IsOrdinal
método, ele retornará True:
era
V1: ValorTV;
começar
V1:= 100;
se V1.IsOrdinal então

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

462 - 16: reflexão e atributos

Log(IntToStr(V1.AsInteger));

No entanto, você não pode usar o método AsString , o que geraria uma conversão de tipo inválida
exceção:

era
V1: ValorTV;
começar
V1:= 100;
Log(V1.AsString);

Porém, se você precisar de uma representação de string, poderá usar o método ToString , que possui uma
instrução case grande tentando acomodar a maioria dos tipos de dados:

era
V1: ValorTV;
começar
V1:= 100;
Log(V1.ToString);

Você provavelmente poderá entender melhor lendo as palavras de Barry Kelly, ex-membro da equipe de
P&D da Embarcadero que trabalhou no RTTI:

TValue é o tipo usado para empacotar valores de e para chamadas baseadas em RTTI para
métodos e leituras e gravações de campos e propriedades.
É um pouco parecido com o Variant, mas muito mais sintonizado com o sistema do tipo Object
Pascal; por exemplo, instâncias podem ser armazenadas diretamente, bem como conjuntos,
referências de classe, etc. Também é digitado de forma mais estrita e não faz (por exemplo)
conversões silenciosas de string para número.

Agora que você entende melhor sua função, vamos dar uma olhada nas capacidades reais do registro
TValue . Possui um conjunto de métodos de nível superior para atribuir e extrair os valores reais, além de
um conjunto de métodos baseados em ponteiros de baixo nível. Vou me concentrar no primeiro grupo.
Para atribuir valores, TValue define vários operadores implícitos , permitindo
execute uma atribuição direta como nos trechos de código acima:

operador de classe Implícito (const Valor: string): TValue;


operador de classe Implícito (Valor: Inteiro): TValue;
operador de classe Implícito (Valor: Estendido): TValue;
operador de classe Implícito (Valor: Int64): TValue;
operador de classe Implícito(Valor: TObject): TValue;
operador de classe Implícito(Valor: TClass): TValue;
operador de classe Implícito (Valor: Booleano): TValue;

O que todos esses operadores fazem é chamar o método de classe genérica From :

função de classe From<T>(const Valor: T): TValue; estático;

Ao chamar essas funções de classe você precisa especificar o tipo de dados e também passar um valor
desse tipo, como o código a seguir substituindo a atribuição do valor 100 dos trechos de código anteriores:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 463

V1 := TValue.From<Integer>(100);

Esta é uma espécie de técnica universal para mover qualquer tipo de dados para um TValue. Depois que os
dados forem atribuídos, você poderá usar vários métodos para testar seu tipo:

propriedade Tipo: TTypeKind leia GetTypeKind; função


IsObject: Boolean; função IsClass:
Boolean; função IsOrdinal:
Booleano; função IsType<T>:
Booleano; sobrecarga; função IsArray: Booleano;

Observe que o IsType genérico pode ser usado para praticamente qualquer tipo de dados.

Existem métodos correspondentes para extrair os dados, mas novamente você pode usar apenas o método
compatível com os dados reais armazenados no TValue, já que nenhuma conversão está ocorrendo:

função AsObject: TObject; função


AsClass: TClass; função
AsOrdinal: Int64; função AsType<T>:
T; função AsInteger: Inteiro;
função AsBoolean: Boolean; função
AsExtended: Estendida; função
AsInt64: Int64; função AsInterface:
IInterface; função AsString:
string; função AsVariant: Variante; função
AsCurrency: Moeda;

Alguns desses métodos duplicam com uma versão Try que retorna False, em vez de gerar uma exceção, no
caso de um tipo de dados incompatível. Existem também alguns métodos de conversão limitados, sendo os
mais relevantes o Cast genérico e a função ToString que já usei no código:

função Cast<T>: TValue; sobrecarga; função


ToString: string;

Lendo uma propriedade com TValue


A importância do TValue reside no fato de ser esta a estrutura utilizada no acesso às propriedades e valores dos
campos utilizando o RTTI estendido e a unidade Rtti . Como um exemplo real do uso de TValue, podemos usar
este tipo de registro para acessar tanto uma propriedade publicada quanto um campo privado de um objeto
TButton , como no código a seguir (parte do exemplo RttiAccess ):

era
Contexto: TRttiContext;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

464 - 16: reflexão e atributos

AType: TRttiType;
Propriedade A: TRttiProperty;
ValorA: ValorTV;
Campo AF: TRttiField;
começar
AType := Context.GetType(TButton);
AProperty := AType.GetProperty(); 'Texto'
AValue := AProperty.GetValue(Remetente);
Mostrar(AValue.AsString);

AField := AType.GetField(AValue :='FDesignInfo' );


AField.GetValue(Sender);
Mostrar(AValue.AsInteger.ToString);
fim;

Invocando Métodos
O novo RTTI estendido não apenas permite acessar valores e campos, mas também fornece uma
maneira simplificada de chamar métodos. Neste caso você tem que definir um TValue
elemento para cada parâmetro do método. Existe uma função Invoke global que você
pode chamar para executar um método:

função Invoke(CodeAddress: Ponteiro; const Args: TArray<TValue>;


ChamadaConvenção: TCallConv; AResultType: PTypeInfo): TValue;

Como alternativa melhor, existe um método sobrecarregado Invoke simplificado na classe TRt-
tiMethod :

função Invocar(Instância: TObject;


const Args: array de TValue): TValue; sobrecarga;

Dois exemplos de invocação de métodos usando esta segunda forma simplificada (um retornando um valor
e o segundo exigindo um parâmetro) fazem parte do exemplo RttiAccess e estão listados abaixo:

era
Contexto: TRttiContext;
AType: TRttiType;
Método AM: Método TRtti;
TheValues: matriz de TValue;
ValorA: ValorTV;
começar
AType := context.GetType(TButton);
AMethod := 'Para sequenciar');
AType.GetMethod(TheValues:= [];
AValue := AMethod.Invoke(Sender, TheValues);
Mostrar(AValue.AsString);

AType := Context.GetType(TForm1);
AMethod := AType.GetMethod(); 'Mostrar'

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 465

SetLength(OsValores, 1);
OsValores[0] := AValor;
AMethod.Invoke(Self, TheValues);
fim;

Usando atributos
A primeira parte deste capítulo deu a você uma boa compreensão do RTTI estendido gerado pelo compilador Object
Pascal e dos recursos de acesso RTTI introduzidos pela nova unidade Rtti . Na segunda parte do capítulo podemos
finalmente nos concentrar em um dos principais motivos pelos quais toda essa arquitetura foi introduzida: a
possibilidade de definir atributos customizados e estender o RTTI gerado pelo compilador de maneiras específicas.
Veremos essa tecnologia de uma perspectiva bastante abstrata e depois nos concentraremos nas razões pelas quais
este é um importante passo à frente para o Object Pascal; e faremos isso examinando exemplos práticos.

O que é um atributo?

Um atributo em Object Pascal e C#, ou uma anotação, no jargão Java, é um comentário ou indicação que você pode
adicionar ao seu código-fonte aplicando-o a um tipo, campo, método ou propriedade que o compilador irá
incorporar. o programa. Isso geralmente é indicado entre colchetes, como em:

tipo
[MeuAtributo]
TMyClass = turma
...

Ao ler essas informações em tempo de design, em uma ferramenta de desenvolvimento ou em tempo de execução, no
aplicação final, um programa pode mudar seu comportamento dependendo dos valores que encontrar.

Geralmente os atributos não são usados para alterar os recursos principais reais de uma classe de objetos, mas
sim para permitir que essas classes especifiquem outros mecanismos dos quais podem participar. Declarar uma
classe como serializável não afeta seu código de forma alguma, mas permite que o serial -ização do código saiba que
ele pode operar nessa classe e como (caso você forneça mais
informações junto com o atributo ou outros atributos que marcam os campos ou propriedades da classe).

Foi exatamente assim que o RTTI original e limitado foi usado dentro do Object Pascal. Propriedades marcadas
como publicadas podem aparecer no inspetor de objetos, ser transmitidas para um arquivo DFM e acessadas
em tempo de execução. Os atributos permitem que esse mecanismo se torne muito mais flexível e poderoso. Eles
também são muito mais complexos de usar e fáceis de usar indevidamente, assim como quaisquer recursos de
linguagem poderosos. Além disso, só porque esse recurso existe

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

466 - 16: reflexão e atributos

não significa que você deva jogar fora todas as coisas boas que sabe sobre programação orientada a
objetos para adotar esse novo modelo, mas sim complementar uma com a outra.

Por exemplo, uma classe de funcionário ainda será representada numa hierarquia como uma classe
derivada de uma classe de pessoa; um objeto funcionário ainda terá um ID para seu crachá; mas você
pode “marcar” ou “anotar” a classe do funcionário como uma classe que pode ser mapeada para uma
tabela de banco de dados ou exibida por um formulário de tempo de execução específico. Portanto,
temos herança (é-a), propriedade (tem-a) e anotações (marcadas como) como três mecanismos separados
que você pode usar ao projetar um aplicativo para um design mais flexível e desacoplado.

Depois de ver os recursos do compilador que suportam atributos personalizados no Object Pascal e
observar alguns exemplos práticos, a ideia abstrata que acabei de mencionar deve se tornar mais
compreensível, ou pelo menos essa é a minha esperança!

Classes de atributos e declarações de atributos

Como você define uma nova classe de atributos (ou categoria de atributos)? Você deve herdar da nova
classe TCustomAttribute disponível na unidade System :

tipo
SimpleAttribute = classe(TCustomAttribute)
fim;

O nome da classe que você der à classe de atributo se tornará o símbolo a ser usado no código-
fonte, com a exclusão opcional do sufixo Attribute . Então, se você nomear sua classe como
SimpleAttribute, você poderá usar, no código, um atributo chamado Simple ou SimpleAttribute. Por esta
razão o clássico T inicial para classes Object Pascal geralmente não é usado no caso de atributos, sendo
a principal exceção System.TCustomAttribute.

tipo
[Simples]
TMyClass = classe(TObject)
público
[Simples]
procedimento Um;

Neste caso apliquei o atributo Simple à classe como um todo e a um método.


Ao lado de um nome, um atributo pode suportar um ou mais parâmetros. Os parâmetros passados
para um atributo devem corresponder aos indicados no construtor da classe do atributo, se houver.

tipo
ValueAttribute = classe(TCustomAttribute)
privado
FValor: Inteiro;
público
construtor Criar (N: Inteiro);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 467

valor da propriedade: leitura inteira FValue;


fim;

É assim que você pode aplicar este atributo com um parâmetro:

tipo
[Valor(22)]
TMyClass = classe(TObject)
público
[Valor(0)]
procedimento dois;

Os valores dos atributos, passados ao seu construtor, devem ser expressões constantes, pois são
resolvidos em tempo de compilação. É por isso que você está limitado a apenas alguns tipos de
dados: valores ordinais, strings, conjuntos e referências de classe. Do lado positivo, você
pode ter vários construtores sobrecarregados com parâmetros diferentes. Observe que você pode
aplicar vários atributos ao mesmo símbolo, como fiz no exemplo RttiAttrib , que resume os
trechos de código desta seção:

tipo
[Simples][Valor(22)]
TMyClass = classe(TObject)
público
[Simples]
procedimento Um;
[Valor(0)]
procedimento dois;
fim;

E se você tentar usar um atributo que não está definido (talvez por causa de uma instrução de uso
ausente)? Infelizmente, você recebe uma mensagem de aviso muito enganosa:

[Aviso DCC] RttiAttribMainForm.pas(44): W1025


Recurso de idioma não suportado: 'atributo personalizado'

O fato de ser um aviso implica que o atributo será ignorado, então você deve tomar cuidado
para esses avisos, como você deveria fazer em qualquer caso de aviso, ou melhor ainda, tratar o
aviso de “recurso de idioma não suportado” como um erro (algo que você pode fazer no
Página Dicas e Avisos da caixa de diálogo Opções do Projeto):

[Erro DCC] RttiAttribMainForm.pas(38):


E1025 Recurso de idioma não suportado: 'atributo personalizado'

Finalmente, em comparação com outras implementações do mesmo conceito, atualmente não há


como limitar o escopo dos atributos, como declarar que um atributo pode ser aplicado a um tipo, mas
não a um método. O que está disponível no editor, em vez disso, é suporte completo para
atributos na refatoração de renomeação. Você não só pode alterar o nome do atributo
classe, mas o sistema irá atender quando o atributo for usado tanto em seu nome completo quanto
sem a parte final do “atributo” .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

468 - 16: reflexão e atributos

note A refatoração de atributos foi mencionada pela primeira vez por Malcolm Groves em
seu blog em http://www.malcolmgroves.com/blog/?p=554

Atributos de navegação
Agora, esse código pareceria totalmente inútil se não houvesse uma maneira de descobrir quais
atributos estão definidos e possivelmente injetar um comportamento diferente em um objeto por causa
desses atributos. Deixe-me começar a focar na primeira parte. As classes da unidade Rtti permitem
descobrir quais símbolos possuem atributos associados.

Este é o código, extraído do exemplo RttiAttrib , mostrando a lista de atributos da classe atual:

procedimento TMyClass.One;
era
Contexto: TRttiContext; Atributos:
TArray<TCustomAttribute>; Atributo: TCustomAttribute;
Atributos iniciais :=

Context.GetType(ClassType).GetAttributes; para Atributo em Atributos faça

Form39.Log(Atributo.ClassName);

A execução deste código imprimirá:

Atributo Simples
Atributo de valor

Você pode estendê-lo adicionando o seguinte código ao loop for-in para extrair o valor específico do tipo de
atributo fornecido:

se Attrib for ValueAttribute então


Form39.Show(' -' + IntToStr(ValueAttribute(Atributo).Valor));

Que tal buscar os métodos com um determinado atributo, ou com qualquer atributo? Você não pode
filtrar os métodos antecipadamente, mas tem que passar por cada um deles, verificar seus atributos e
ver se é relevante para você. Para ajudar nesse processo, escrevi uma função que verifica se um
método suporta um determinado atributo:

tipo
TCustomAttributeClass = classe de TCustomAttribute;

função HasAttribute(AMethod: TRttiMethod;


AttribClass: TCustomAttributeClass): Boolean;
era
Atributos: TArray<TCustomAttribute>; Atributo:
TCustomAttribute; começar Resultado:
= Falso;
Atributos :=
AMethod.GetAttributes;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 469

para Atributo em Atributos faça


se Attrib.InheritsFrom(AttribClass) então
Sair(Verdadeiro);
fim;

A função HasAttribute é chamada pelo programa RttiAttrib para verificar o atributo Simple específico e
ao mesmo tempo listar quaisquer outros atributos que encontrar:

era
Contexto: TRttiContext;
AType: TRttiType;
Método AM: Método TRtti;
começar
AType := Context.GetType(TMyClass);

Log 'Métodos marcados com o atributo [Simple]' );


(para AMethod em AType.GetMethods faça
se HasAttribute(AMethod, SimpleAttribute) então
Mostrar(AMethod.Nome);
''
);
'Métodos marcados com atributo'any );
Log(Log(para AMethod em AType.GetMethods do
se HasAttribute(AMethod, TCustomAttribute) então
Mostrar(AMethod.Nome);

O efeito é listar os métodos marcados com o atributo fornecido ou com qualquer atributo:

Métodos marcados com o atributo [Simple]


Um

Métodos marcados com qualquer atributo


Um
Dois

Em vez de simplesmente descrever atributos, o que geralmente se faz é adicionar algum comportamento
independente determinado pelos atributos de uma classe, em vez de seu código real. Como exemplo,
posso injetar um comportamento específico no código anterior: O objetivo poderia ser chamar todos os
métodos de uma classe marcada com um determinado atributo, considerando-os como métodos sem
parâmetros:

procedimento TForm39.BtnInvokeIfZeroClick(Sender: TObject);


era
Contexto: TRttiContext;
AType: TRttiType;
Método AM: Método TRtti;
ATarget: TMyClass;
ZeroParams: array de TValue;
começar
ATarget := TMyClass.Create;
tentar
AType := Context.GetType(ATarget.ClassType);
para AMethod em AType.GetMethods faça
se HasAttribute(AMethod, SimpleAttribute) então

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

470 - 16: reflexão e atributos

AMethod.Invoke(ATarget, ZeroParams);
finalmente
ATarget.Grátis;
fim;
fim;

O que esse trecho de código faz é criar um objeto, capturar seu tipo, verificar um determinado
atributo e invocar cada método que possui o atributo Simple . Em vez de herdar
a partir de uma classe base, implementando uma interface ou escrevendo um código específico para
realizar a solicitação, tudo o que precisamos fazer para obter o novo comportamento é marcar um ou mais
métodos com um determinado atributo. Não que este exemplo torne o uso de atributos extremamente óbvio.
Para alguns padrões comuns no uso de atributos e alguns estudos de caso reais, você pode consultar a
parte final deste capítulo.

Interceptadores de métodos virtuais


Esta seção aborda um recurso muito avançado do Object Pascal e você pode querer pular a
leitura se estiver apenas começando a aprender a linguagem Delphi. Destina-se a leitores mais
experientes.
Há outro recurso relevante que foi adicionado após a introdução do RTTI estendido, que é a capacidade
de interceptar a execução de métodos virtuais de uma classe existente, criando uma classe proxy para
um objeto existente. Em outras palavras, você pode pegar um objeto existente e alterar seus métodos
virtuais (um específico ou todos de uma vez).

Por que você quer fazer isso? Em uma aplicação Object Pascal padrão, você provavelmente não usaria
esse recurso. Se você precisar de um objeto com comportamento diferente, basta alterá-lo ou criar uma
subclasse. As coisas são diferentes para as bibliotecas, pois as bibliotecas devem ser escritas de uma
forma muito genérica, sabendo pouco sobre os objetos que poderão manipular, e
impondo o mínimo de carga possível sobre os próprios objetos. Este é o tipo de cenário que os
interceptores de métodos virtuais foram adicionados para manipular no Object Pascal.

note Uma postagem de blog muito detalhada de Barry Kelly sobre Virtual Method Interceptors (ao qual devo muito) está em
disponível em http://blog.barrkel.com/2010/09/virtual-method-interception.html.

Antes de nos concentrarmos nos cenários possíveis, deixe-me discutir a tecnologia em si. Suponha que
você tenha uma classe existente com pelo menos um método virtual, como o seguinte:

tipo
TPpessoa = turma
...
público
nome da propriedade: string lida FName escreve SetName;
propriedade Data de nascimento: TDate leitura FBirthdate gravação SetBirthdate;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 471

função Idade: Inteiro; virtual;


função ToString: string; sobrepor;
fim;

função TPpessoa.Idade: Inteiro;


começar
Resultado: = Anos entre (data, data de nascimento);
fim;

função TPerson.ToString: string;


começar
Resultado := FNome +
' é ' '
+ IntToStr(Idade) + anos' ;
fim;

Suporte, você tem um objeto FPerson1 desta classe. Agora o que você pode fazer é criar um
objeto TVirtualMethodInterceptor (uma nova classe definida na unidade RTTI) vinculado à classe do
objeto que deseja subclassificar (TPerson), alterando a propriedade estática do objeto FPerson1 .
classe para a dinâmica:

era
FVmi: TVirtualMethodInterceptor;
começar
FVmi := TVirtualMethodInterceptor.Create(TPerson);
FVmi.Proxify(FPerson1);

Depois de ter o objeto FVmi , você pode instalar manipuladores especiais para seus eventos (OnBefore,
OnAfter e OnException) usando métodos anônimos. Eles serão acionados antes de qualquer
chamada de método virtual, após qualquer chamada de método virtual e no caso de uma
exceção em um método virtual. Estas são as assinaturas para os três tipos de métodos anônimos:

TInterceptBeforeNotify = referência ao procedimento(


Instância: TObject; Método: TRttiMethod;
const Args: TArray<TValue>; fora DoInvoke: Boolean;
resultado: TValue);
TInterceptAfterNotify = referência ao procedimento(
Instância: TObject; Método: TRttiMethod;
const Args: TArray<TValue>; var Resultado: TValue);
TInterceptExceptionNotify = referência ao procedimento(
Instância: TObject; Método: TRttiMethod;
const Args: TArray<TValue>; fora RaiseException: Boolean;
A Exceção: Exceção; resultado: TValue);

Em cada evento você obtém o objeto, a referência do método, os parâmetros e o resultado


(que pode já estar definido ou não). No evento OnBefore você pode definir o DoInvoke
parâmetro para desabilitar a execução padrão, enquanto, no evento OnExcept , você obtém
informações sobre a exceção.

No exemplo InterceptBaseClass , que usa a classe TPerson acima, interceptei os métodos virtuais
da classe com este código de registro:

procedimento TFormIntercept.BtnInterceptClick(Sender: TObject);


começar

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

472 - 16: reflexão e atributos

FVmi := TVirtualMethodInterceptor.Create(TPerson);
FVmi.OnBefore := procedimento(Instância: TObject; Método: TRttiMethod;
const Args: TArray<TValue>; fora DoInvoke: Boolean;
resultado: TValue)
começar
'
'Antes de ligar + Método.Nome);
Mostrar(fim;
FVmi.OnAfter := procedimento(Instância: TObject; Método: TRttiMethod;
const Args: TArray<TValue>; var Resultado: TValue)
começar
'
'Depois de ligar + Método.Nome);
Mostrar(fim;
FVmi.Proxify(FPerson1);
fim;

Observe que o objeto FVmi precisa ser mantido pelo menos até que o objeto FPerson1 esteja em uso,
ou você usará uma classe dinâmica que não está mais disponível e chamará métodos anônimos
que já foram lançados. Na demonstração salvei-o como um campo de formulário (FVmi), assim como
o objeto ao qual se refere (FPerson1).
O programa usa o objeto chamando seus métodos e verificando o nome da classe base:
'
Mostrar( 'Idade: + IntToStr(FPerson1.Idade));
'
Mostrar( 'Pessoa:
'Aula: ' +FPerson1.ToString);
Mostrar( +FPerson1.ClassName);
'
Mostrar( 'Classe base: + FPerson1.ClassParent.ClassName);

Antes de instalar o interceptor, a saída é:

Idade: 26
Pessoa: Mark tem 26 anos
Classe: TP Pessoa
Classe Base: TObject

Depois de instalar o interceptor, a saída será:

Antes de ligar para Idade


Depois de ligar para Age
Idade: 26
Antes de chamar ToString
Antes de ligar para Idade
Depois de ligar para Age
Depois de chamar ToString
Pessoa: Mark tem 26 anos
Classe: TP Pessoa
Classe Base: TPerson

Observe que a classe tem o mesmo nome da classe base, mas na verdade é uma classe
diferente, a classe dinâmica criada pelo interceptador de método virtual.
Embora não exista uma maneira oficial de restaurar a classe do objeto de destino para o original,
a classe em si está disponível no objeto FVmi e também como classe base do objeto.
Ainda assim, você pode usar força bruta para atribuir aos dados de classe do objeto (seus
quatro bytes iniciais) a referência de classe correta:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 473

PPointer(FPerson1)^ := FVmi.OriginalClass;

Como exemplo adicional, modifiquei o código OnBefore para que, caso você esteja ligando
Age, ele retorna um determinado valor e ignora a execução do método real:

FVmi.OnBefore := procedimento(Instância: TObject; Método: TRttiMethod;


const Args: TArray<TValue>; fora DoInvoke: Boolean;
resultado: TValue)
começar
'
Mostrar 'Antes de ligar + Método.Nome);
(se Método.Nome = 'Idade' então
começar
Resultado:= 33;
DoInvoke := Falso;
fim;
fim;

A saída muda para o seguinte (observe que as chamadas Age e o relativo OnAfter
eventos são ignorados):

Antes de ligar para Idade


Idade: 33
Antes de chamar ToString
Antes de ligar para Idade
Depois de chamar ToString
Pessoa: Mark tem 33 anos
Classe: TP Pessoa
Classe Base: TPerson

Agora que vimos os detalhes técnicos por trás dos interceptadores de métodos virtuais, podemos
volte para descobrir em quais cenários você gostaria de usar esse recurso.

Novamente, basicamente não há razão para usar isso em um aplicativo padrão. O foco, por outro
lado, é principalmente para quem desenvolve bibliotecas avançadas e precisa implementar um
comportamento customizado para testar ou processar objetos.

Por exemplo, isso poderia ajudar a construir uma biblioteca de testes unitários, embora fosse limitada
apenas a métodos virtuais. Você também possivelmente usaria isso junto com atributos personalizados
para implementar um estilo de codificação semelhante à programação orientada a aspectos.

Estudos de Caso RTTI


Agora que abordei os fundamentos do RTTI e o uso de atributos, vale a pena examinar algumas
situações do mundo real nas quais o uso dessas técnicas será útil. Existem muitos cenários em que um
RTTI mais flexível e a capacidade de customizá-lo através de atributos são relevantes, mas não
tenho espaço para uma longa lista de situações.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

474 - 16: reflexão e atributos

ções. Em vez disso, o que farei é guiá-lo no desenvolvimento passo a passo de dois exemplos simples, mas
significativos.

O primeiro programa de demonstração mostrará o uso de atributos para identificar informações específicas dentro de
uma classe. Em particular, queremos ser capazes de inspecionar um objeto de uma classe que declara fazer parte
de uma arquitetura e possuir uma descrição e um ID único referente ao próprio objeto. Isso pode ser útil em diversas
situações, como na descrição de objetos armazenados em uma coleção (seja genérica ou tradicional).

A segunda demonstração será um exemplo de streaming, especificamente streaming de uma classe para um arquivo
XML. Começarei pela abordagem clássica de usar o RTTI publicado, para depois passar para o novo RTTI estendido
e, finalmente, mostrarei como você pode usar atributos para personalizar o código e torná-lo mais flexível.

Atributos para ID e Descrição


Se você deseja compartilhar alguns métodos entre vários objetos, a abordagem clássica era definir uma
classe base com métodos virtuais e herdar os vários objetos da classe base, substituindo os métodos virtuais.
Isso é legal, mas apresenta muitas restrições em termos de classes que podem participar da arquitetura, já que você
tem uma classe base fixa.

Uma técnica padrão para superar esta situação é usar uma interface em vez de uma classe base comum.
Múltiplas classes implementando a interface (mas sem nenhuma classe ancestral comum) podem fornecer uma
implementação dos métodos da interface, que agem muito
da mesma forma que os métodos virtuais.

Um estilo totalmente diferente (com vantagens e desvantagens) é o uso de atributos para marcar classes
participantes e determinados métodos (ou propriedades). Isto abre mais flexibilidade, não envolve interfaces, mas é
baseado em um sistema comparativamente lento e
processo de pesquisa de informações em tempo de execução propenso a erros, em vez de uma resolução em
tempo de compilação. Isso significa que não estou defendendo esse estilo de codificação em vez de
interfaces como uma abordagem melhor, apenas como algo que pode valer a pena avaliar e ser interessante de
usar em algumas circunstâncias.

A classe de atributos de descrição


Para esta demonstração, defini um atributo com uma configuração que indica o elemento ao qual ele está sendo
aplicado. Eu poderia ter usado três atributos diferentes, mas prefiro evitar poluir o
espaço para nome do atributo. Esta é a definição da classe de atributos:

tipo
TDescriptionAttrKind = (dakClass, dakDescription, dakId);

DescriçãoAttribute = class(TCustomAttribute)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 475

privado
FDak: TDescriptionAttrKind; construtor
público
Create(ADak: TDescriptionAttrKind = dakClass); propriedade Tipo: TDescriptionAttrKind lê
FDak; fim;

Observe o uso do construtor com um valor padrão para seu único parâmetro para permitir que você use
o atributo sem parâmetros.

As aulas de amostra
A seguir escrevi duas classes de exemplo que usam o atributo. Cada classe é marcada com o atributo
e possui dois métodos marcados com o mesmo atributo customizados com diferentes tipos.

O primeiro (TPerson) tem a descrição mapeada para a função GetName e usa seu método
TObject.GetHashCode para fornecer um ID temporário, declarando novamente o método para aplicar o
atributo a ele (o código do método é simplesmente uma chamada para a versão herdada ) :

tipo
[Descrição]
TPpessoa = aula
particular
Data de nascimento: TDate;
NomeF: string;
FCaís: string; procedimento
SetBirthdate(const Valor: TDate); procedimento SetCountry(const
Valor: string); procedimento SetName(const Valor: string); público

[Descrição(dakDescrição)] função GetName:


string;
[Descrição(dakID)] função
GetStringCode: Integer; propriedade publicada
Nome: string
read GetName write SetName; propriedade Data de nascimento: TDate leitura
FBirthdate gravação SetBirthdate; propriedade País: string lida FCountry escreve SetCountry;
fim;

A segunda classe (TCompany) é ainda mais simples, pois possui valores próprios para o ID e a descrição:

tipo
[Descrição]
TCompany = classe
privada
NomeF: string;
FCaís: string;
FID: sequência;
procedimento SetName(const Valor: string); procedimento
SetID (valor const: string);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

476 - 16: reflexão e atributos

público
[Descrição(dakDescrição)]
função GetNome: string;
[Descrição(dakID)]
função GetID: string;
Publicados
nome da propriedade: string read GetName write SetName;
propriedade País: string lida FCountry escreve FCountry;
ID da propriedade: string lida FID gravação SetID;
fim;

Mesmo que existam semelhanças entre as duas classes, elas não estão totalmente relacionadas em
termos de hierarquia, interface comum ou algo parecido. O que eles compartilham é o uso do mesmo
atributo.

O Projeto de Amostra e Navegação de Atributos


O atributo shared é utilizado para exibir informações sobre objetos adicionados a uma lista,
declarados no formulário principal do programa como:

FObjectsList: TObjectList<TObject>;

Esta lista é criada e inicializada quando o programa é iniciado:

procedimento TFormDescrAttr.FormCreate(Remetente: TObject);


era
Pessoa: TP Pessoa;
Empresa A: Empresa T;
começar
FObjectsList := TObjectList<TObject>.Create;

// Adicione um pessoa
APerson := TPerson.Create;
APessoa.Nome := 'Wiley' ;
APerson.Country := 'Deserto' ;
APerson.BirthDate := Data - 1000;
FObjectsList.Add(APerson);

// Adicione um empresa
AEmpresa := TEmpresa.Create;
AEmpresa.Nome := ; 'ACME Inc.'
ACompany.ID := IntToStr(GetTickCount);
AEmpresa.País := 'Mundialmente' ;
FObjectsList.Add(AEmpresa);

// Adicione um objeto não relacionado


FObjectsList.Add(TStringList.Create);

Para exibir informações sobre os objetos (nomeadamente o ID e a descrição, se disponível) o programa


utiliza a descoberta de atributos via RTTI. Primeiro, ele usa uma função auxiliar para determinar se a
classe está marcada com o atributo específico:

função TypeHasDescription (AType: TRttiType): Boolean;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 477

era
Atributo: TCustomAttribute; comece
para
Attrib em AType.GetAttributes comece se Attrib for

DescriptionAttribute então Exit(True); fim; Resultado := Falso;


fim;

note Neste caso você precisa verificar o nome completo da classe, DescriptionAttribute, e não apenas
“Descrição”, que é o símbolo que você pode usar ao aplicar o atributo, pois o compilador
reconhece o nome abreviado apenas quando usado entre colchetes.

Se for esse o caso, o programa prossegue obtendo cada atributo de cada método, com um loop
aninhado, e verificando se este é o atributo que procuramos:

se TypeHasDescription(AType) então comece para


AMethod
em AType.GetMethods do
para Atributo em AMethod.GetAttributes faça
se Attrib for DescriptionAttribute então
...

No centro do loop, os métodos marcados com atributos são invocados para ler os resultados em
duas strings temporárias (posteriormente adicionadas à interface do usuário):

se Attrib for DescriptionAttribute então


case DescriçãoAttribute(Attrib).Tipo de dakClass: ; dakDescription:
strDescr := // Ignorar
AMethod.Invoke(AnObject,
[]).ToString; dakId: strID := AMethod.Invoke(AnObject, []).ToString;

O que o programa não consegue fazer é verificar se um atributo está duplicado (ou seja, se existem
vários métodos marcados com o mesmo atributo, situação em que você pode querer gerar uma
exceção). Resumindo todos os trechos da página anterior, este é o código completo do método
UpdateList :

procedimento TFormDescrAttr.UpdateList;
era
UmObjeto: TObject;
Contexto: TRttiContext;
AType: TRttiType;
Atributo: TCustomAttribute;
Método AM: Método TRtti;
StrDescr,StrID: string; comece para

AnObject em FObjectsList comece

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

478 - 16: reflexão e atributos

AType := Context.GetType(AnObject.ClassInfo);
se TypeHasDescription(AType) então
começar
para AMethod em AType.GetMethods faça
para Atributo em AMethod.GetAttributes faça
se Attrib for DescriptionAttribute então
caso DescriçãoAtributo(Atributo).Tipo de
dakClass: ; // Ignorar
dakDescrição:
// Deve verificar se o atributo está duplicado
StrDescr := aMethod.Invoke(
AnObject, []).ToString;
dataId:
StrID := AMethod.Invoke(
AnObject, []).ToString;
fim;
// Parei de procurar atributos
// Deve verificar se encontrounós
alguma coisa
com ListView1.Items.Add faça
começar
Texto := STypeName;
Detalhe := StrDescr;
fim;
fim;
fim;
// Caso contrário, ignore o objeto, poderia levantar um exceção
fim;

Se este programa produz resultados bastante desinteressantes, a forma como isso é feito é relevante, pois
marquei algumas classes e dois métodos dessas classes com um atributo e consegui processar essas classes
com um algoritmo externo.

Em outras palavras, as próprias classes não precisam de nenhuma classe base específica, nem de
implementação de interface, nem de qualquer código interno para fazer parte da arquitetura, mas apenas precisam declarar
eles querem participar usando atributos. A responsabilidade total pelo gerenciamento das classes está em
algum código externo.

Transmissão XML
Um caso interessante e muito útil para usar RTTI é criar uma representação “externa” portátil de um objeto,
para salvar seu status em um arquivo ou enviá-lo para outra aplicação. Tradicionalmente, a abordagem Object
Pascal para este problema tem sido
para transmitir as propriedades publicadas de um objeto, a mesma abordagem usada ao criar arquivos DFM.
Uma abordagem opcional tem sido escrever ou usar estruturas de streaming personalizadas para serializar
e desserializar objetos de e para streams.

Agora o RTTI permite salvar os dados reais do objeto, seus campos, em vez da interface externa. Isto é
mais poderoso, embora possa levar a uma complexidade extra, por

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 479

exemplo na gestão dos dados de objetos internos. Novamente, a demonstração funciona como uma
simples vitrine da técnica e não se aprofunda em todas as suas implicações.

Este exemplo vem em três versões compiladas em um único projeto para simplificar. A primeira é a
abordagem tradicional do Object Pascal baseada em propriedades publicadas, a segunda usa RTTI
estendido e campos, a terceira usa atributos para customizar o mapeamento de dados.

A classe Trivial do gravador XML

Para ajudar na geração do XML, baseei o exemplo XmlPersist em uma versão estendida de uma
classe TTrivialXmlWriter que escrevi originalmente em meu Delphi 2009 Handbook para demonstrar o
uso da classe TTextWriter , que não quero abordar
novamente aqui. Basta dizer que a classe pode acompanhar os nós XML que abre, graças a uma pilha
de strings, e fechar os nós XML em uma ordem LIFO (último a entrar, primeiro a sair).

note O código fonte da classe TTrivialXmlWriter , do Delphi 2009 Handbook, pode ser encontrado em http://github.com/
MarcoDelphiBooks/Delphi2009Handbook/tree/master/07/ReaderWriter

À classe original adicionei algum código de formatação limitada e três métodos para salvar um objeto,
com base nas três abordagens diferentes que explorarei nesta seção. Esta é a declaração completa da
classe:

tipo
TTrivialXmlWriter = classe
privado
FWriter: TTextWriter;
FNodes: TStack<string>;
FOwnsTextWriter: Booleano;
público
construtor Criar (AWriter: TTextWriter); sobrecarga;
construtor Criar (AStream: TStream); sobrecarga;
destruidor Destruir; sobrepor;
procedimento WriteStartElement(const SName: string);
procedimento WriteEndElement(Recuo: Boolean = False);
procedimento WriteString(const SValue: string);
procedimento WriteObjectPublished(AnObj: TObject);
procedimento WriteObjectRtti(AnObj: TObject);
procedimento WriteObjectAttrib(AnObj: TObject);
função Recuo: string;
fim;

Para se ter uma ideia do código, este é o método WriteStartElement , que utiliza o
Função de recuo para deixar o dobro de espaços do número atual de nós na pilha interna:

procedimento TTrivialXmlWriter.WriteStartElement(const SName: string);


começar
FWriter.Write(Recuo + '<' + SName + '>');
FNodes.Push(SNome);

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

480 - 16: reflexão e atributos

fim;

Você encontrará o código completo da classe no código-fonte do projeto.

Streaming clássico baseado em RTTI


Após esta introdução, cobrindo a classe de suporte, deixe-me começar do início, ou seja,
salvando um objeto em um formato baseado em XML usando o RTTI clássico para
propriedades publicadas.

O código do método WriteObjectPublished é bastante complexo e requer um pouco de explicação. É baseado na


unidade TypInfo e usa a versão de baixo nível do antigo RTTI para poder obter a lista de propriedades
publicadas para um determinado objeto (o AnObj
parâmetro), com código como:

NProps := GetTypeData(AnObj.ClassInfo)^.PropCount;
GetMem(PropList, NProps * SizeOf(Pointer));
GetPropInfos(AnObj.ClassInfo, PropList);
para I := 0 para NProps - 1 faça
...

O que isso faz é solicitar o número de propriedades, alocar uma estrutura de dados de
tamanho adequado e preencher a estrutura de dados com informações sobre as propriedades publicadas.
Caso você esteja se perguntando se poderia escrever esse código de baixo nível? Bem, você
acabou de encontrar uma boa razão para a introdução do novo RTTI: para simplificar e ocultar a
complexidade do antigo RTTI.
Para cada propriedade, o programa extrai o valor das propriedades dos tipos numérico e string,
enquanto extrai qualquer subobjeto e atua recursivamente sobre ele:

StrPropName := UTF8ToString(PropList[i].Nome);
case PropList[i].PropType^.Tipo de
tkInteger, tkEnumeration, tkString, tkUString, ...:
começar
WriteStartElement(StrPropNome);
WriteString(GetPropValue(AnObj, StrPropName));
WriteEndElement;
fim;
Classe tk:
começar
InternalObject := GetObjectProp(AnObj, StrPropName);
// Recurso na subclasse
WriteStartElement(StrPropNome);
WriteObjectPublished(InternalObject como TPersistent);
WriteEndElement(Verdadeiro);
fim;
fim;

Existe alguma complexidade extra, mas, por exemplo, e para lhe dar uma ideia da abordagem
tradicional, isso deve ser suficiente.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 481

Para demonstrar o efeito do programa escrevi duas classes (TCompany e TPer-son) adaptadas do exemplo
anterior. Desta vez, porém, a empresa pode ter uma pessoa designada para uma propriedade extra,
chamada Boss. No mundo real isto seria mais complexo, mas, para este exemplo, é uma suposição razoável.
Estas são as propriedades publicadas das duas classes:

tipo
TPpessoa = classe(TPersistente)
...
propriedade
publicada Nome: string lida FName escreve FName; propriedade
País: string lida FCountry escreve FCountry; fim;

TEmpresa = classe(TPersistente)
...
propriedade
publicada Nome: string lida FName escreve FName; propriedade
País: string lida FCountry escreve FCountry; ID da propriedade: string lida FID
gravação FID; propriedade Chefe: TPerson lê FPerson
escreve FPerson; fim;

A forma principal do programa possui um botão utilizado para criar e conectar dois objetos dessas duas
classes e salvá-los em um fluxo XML, que posteriormente será exibido. A seção de streaming possui o
seguinte código:

SS := TStringStream.Create; XmlWri :=
TTrivialXmlWriter.Create(SS);
'Empresa' );
XmlWri.WriteStartElement(XmlWri.WriteObjectPublished(ACompany);
XmlWri.WriteEndElement;

O resultado é um arquivo XML como:

<Empresa>
<Nome>ACME Inc.</Nome>
<País>Em todo o mundo</País>
<ID>29088851</ID>
<Chefe>
<Nome>Wiley</Nome>
<País>Deserto</País>
</Chefe>
</Empresa>

Campos de streaming com RTTI estendido


Com o RTTI de alto nível, disponível no Object Pascal, eu poderia ter convertido esse programa antigo
para usar o RTTI estendido para acessar as propriedades publicadas. O que vou fazer, em vez disso, é
utilizá-lo para salvar a representação interna do objeto, ou seja, seus campos de dados privados. Não estou
apenas fazendo algo mais pesado, mas estou fazendo

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

482 - 16: reflexão e atributos

com código de nível muito superior. O código completo do método WriteObjectRtti é o seguinte:

procedimento TTrivialXmlWriter.WriteObjectRtti(AnObj: TObject);


era
AContext: TRttiContext; AType:
TRttiType; Campo AF:
TRttiField; comece AType :=

AContext.GetType(AnObj.ClassType); para AField em AType.GetFields


comece se AField.FieldType.IsInstance então
comece
WriteStartElement(AField.Name);

WriteObjectRtti(AField.GetValue(AnObj).AsObject);
WriteEndElement(Verdadeiro); fim senão início WriteStartElement(AField.Name);

WriteString(AField.GetValue(AnObj).ToString);
WriteEndElement; fim; fim; fim;

O XML resultante é um pouco semelhante, mas de alguma forma menos limpo, pois os nomes dos campos são
geralmente menos legíveis do que os nomes das propriedades:

<Empresa>
<FName>ACME Inc.</FName>
<FCountry>Em todo o mundo</FCountry>
<FID>29470148</FID>
<FPerson>
<FName>Wiley</FName>
<FCountry>Deserto</FCountry>
</FPerson>
</Empresa>

Outra grande diferença, porém, é que, para que isso funcionasse, as classes não precisavam herdar da classe
TPersistent nem ser compiladas com nenhuma opção especial.

Usando atributos para personalizar streaming


Além do problema com os nomes das tags XML, há outra questão que não mencionei. Usar nomes de tags
XML, que na verdade são símbolos compilados, está longe de ser uma boa ideia. Além disso, no código não há como
excluir algumas propriedades ou campos do streaming baseado em XML.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 483

note O streaming das propriedades do Object Pascal pode ser controlado usando a diretiva armazenada , que pode ser lida
usando a unidade TypInfo . Ainda assim, esta solução está longe de ser simples e limpa, mesmo que o mecanismo
de streaming DFM a utilize de forma eficaz.

Esses são problemas que podemos resolver usando atributos, embora a desvantagem seja ter que usá-los
bastante na declaração de nossas classes, um estilo de codificação que não gosto muito. Para a nova
versão do código, defini um construtor de atributos com um parâmetro opcional:

tipo
XmlAttribute = classe(TCustomAttribute) privado

FTag: string;
construtor ''
público Create(StrTag: string = propriedade );
TagName: string lida FTag; end;

O código de streaming baseado em atributos é uma variação da última versão baseada no RTTI
estendido. A única diferença é que agora o programa chama a função auxiliar CheckXmlAttr para
verificar se o campo possui o atributo Xml e a decoração opcional do nome da tag:

procedimento TTrivialXmlWriter.WriteObjectAttrib(AnObj: TObject);


era
AContext: TRttiContext; AType:
TRttiType; Campo AF:
TRttiField; StrTagNome: string;
comece AType :=

AContext.GetType(AnObj.ClassType); para AField em AType.GetFields


comece se CheckXmlAttr(AField, StrTagName)
então
comece se AField.FieldType.IsInstance então comece

WriteStartElement(StrTagName);

WriteObjectAttrib(AField.GetValue(AnObj).AsObject);
WriteEndElement(Verdadeiro); fim senão início WriteStartElement(StrTagName);

WriteString(AField.GetValue(AnObj).ToString);
WriteEndElement; fim; fim; fim; fim;

O código mais relevante está na função auxiliar CheckXmlAttr :

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

484 - 16: reflexão e atributos

função CheckXmlAttr(AField: TRttiField;


var StrTag: string): Boolean;
era
Atributo: TCustomAttribute;
começar
Resultado := Falso;
para Atributo em AField.GetAttributes faça
se Attrib for XmlAttribute então
começar
StrTag := XmlAttribute(Atributo).TagName;
se StrTag = '' então StrTag := // Valor padrão
AField.Name;
Sair(Verdadeiro);
fim;
fim;

Os campos sem o atributo XML são ignorados e a tag usada na saída XML é personalizável. Para demonstrar
isso, o programa possui as seguintes classes (desta vez omiti as propriedades publicadas da listagem, pois não
são relevantes):

tipo
TAttrPerson = turma
privado
[xml()] 'Nome'
NomeF: string;
[xml]
FCaís: string;
...

TAttrEmpresa = classe
privado
[xml( 'Nome da empresa')]
NomeF: string;
[xml()] 'País'
FCaís: string;
FID: sequência; // Omitido
[xml()] 'O chefe'
FPerson: TAttrPerson;
...

Com essas declarações, a saída XML será semelhante à seguinte (observe o nome da tag, o fato de o ID ser
omitido e o nome padrão (de aparência feia) para o FCountry
campo):

<Empresa>
<NomedaEmpresa>ACME Inc.</NomeEmpresa>
<País>Em todo o mundo</País>
<O Chefe>
<Nome>Wiley</Nome>
<FCountry>Deserto</FCountry>
</TheBoss>
</Empresa>

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

16: reflexão e atributos - 485

A diferença aqui é que podemos ser muito flexíveis sobre quais campos incluir e como nomeá-los no XML, algo
que as versões anteriores não permitiam.

Mesmo que esta seja apenas uma implementação muito esquelética, acho que dar a você a oportunidade de ver a
versão final sendo criada passo a passo, começando com o RTTI clássico, deu a você uma boa noção das
diferenças entre as várias técnicas.

O que é importante ter em mente, na verdade, é que não é certo que usar atributos sempre será a melhor solução!
Por outro lado, deve ficar claro que o RTTI e os atributos agregam muito poder e flexibilidade em qualquer cenário
em que seja necessário inspecionar
a estrutura de um objeto desconhecido em tempo de execução.

Outras bibliotecas baseadas em RTTI


Para concluir este capítulo, gostaria de salientar o fato de que existem diversas bibliotecas, tanto parte do produto
quanto de terceiros, que começaram a aproveitar o RTTI estendido. Um exemplo é o mecanismo de
expressões de ligação que fica por trás do Visual Live Bindings. Você pode criar uma expressão de ligação,
atribuir a ela uma expressão (ou seja, uma sequência de texto com operações como concatenação ou adição) e
fazer com que a expressão se refira a um objeto externo e seu campo.

Mesmo que eu não queira me aprofundar muito neste tópico, que na verdade é uma biblioteca específica e não faz
parte da linguagem ou do sistema central, acho que uma pequena listagem pode lhe dar uma ideia disso:

era
BindExpr: TBindingExpression;
Pessoa: TPpessoa;
começar
Pessoa := TPpessoa.Create;
tentar
NomePessoal := 'João';
Cidade Pessoal := 'São Francisco';

BindExpr := TBindingExpressionDefault.Create;
tentar
"
BindExpr.Source := 'pessoa.nome + mora em " + pessoa.cidade' );
BindExpr.Compile([
TBindingAssociation.Create(Pers, 'pessoa' )]);
Show(BindExpr.Evaluate.GetValue.ToString);
finalmente
BindExpr.Free;
fim;
finalmente
Pessoal Grátis;
fim;
fim;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

486 - 16: reflexão e atributos

Observe que a vantagem aqui vem do fato de você poder alterar a expressão em tempo de execução
(embora, no trecho específico acima, seja uma string constante). A expressão
pode vir de uma edição ou pode ser escolhido dinamicamente em várias expressões possíveis.
É primeiro atribuído ao objeto TBindingExpression e depois analisado e compilado
(que é transformado em uma forma simbólica, não em código assembly) em tempo de execução com a
chamada Com-pile . Ele então usará RTTI quando executado para acessar o objeto TPerson .

A desvantagem é que essa abordagem torna a avaliação da expressão significativamente mais


lenta do que a execução de código-fonte Object Pascal compilado semelhante. Em outras palavras,
é preciso equilibrar o desempenho reduzido com o aumento da flexibilidade. Por exemplo, o modelo Visual
Live Binding construído sobre isso proporciona uma experiência de desenvolvedor muito agradável e
fácil.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 487

17: objeto e o
Unidade de sistema

No coração de qualquer aplicação Object Pascal existe uma hierarquia de classes. Cada classe no
sistema é, em última análise, uma subclasse da classe TObject , portanto, toda a hierarquia tem
uma única raiz. Isso permite usar o tipo de dados TObject como um substituto para o tipo de dados
de qualquer tipo de classe no sistema.
A classe TObject é definida em uma unidade RTL central chamada System, que tem um
papel tão importante que é automaticamente incluída em cada compilação. Embora eu não
aborde todas as classes de unidades do sistema e outras funções de unidades do sistema, há
algumas que vale a pena aprofundar e TObject é certamente a mais importante.

nota Poderia ser debatido longamente se uma classe central do sistema como TObject faz parte da linguagem ou se faz parte de
a Biblioteca de Tempo de Execução (RTL). O mesmo vale para outras funcionalidades da unidade do Sistema , unidade
tão crítica que é automaticamente incluída na compilação de qualquer outra unidade. (Na verdade, é ilegal adicioná-lo a um
usa caluse.) Tal debate seria bastante fútil, então deixarei isso para outro momento.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

488 - 17: objeto e unidade do sistema

A classe TObject
Como acabei de mencionar, a classe TObject é muito especial, pois todas as outras classes herdam dela.
Quando você está declarando uma nova classe, na verdade, se você não estiver indicando uma classe base,
a classe herdará automaticamente de TObject. Em termos de linguagem de programação, esse tipo de cenário é
chamado de hierarquia de classes de raiz única, um recurso que o Object Pascal compartilha com C#, Java e
algumas outras linguagens de programação modernas.
A exceção notável é C++, que não tem conceito de uma única classe base e permite definir múltiplas
hierarquias de classes totalmente separadas.

A classe base TObject não é uma classe que você usaria diretamente criando instâncias dela. No entanto, é
uma classe que você acabará usando sempre que precisar de uma variável que possa conter um objeto de
qualquer tipo e declará-la como uma classe. Um bom exemplo desse uso está em bibliotecas de componentes
e seus manipuladores de eventos que muitas vezes usam TObject como tipo do primeiro parâmetro, geralmente
chamado de Sender. Isso significa que qualquer objeto de qualquer classe pode ser o Remetente. Muitas
coleções genéricas também são coleções de objetos e há vários cenários nos quais o tipo TObject é usado
diretamente.

Nas seções a seguir abordarei alguns dos recursos da classe TObject


isso, por definição, significa que eles se aplicam a todas as classes do Delphi e ao código que você escreve
com ele.

Construção e Destruição

Embora não faça muito sentido criar um TObject diretamente, o construtor e o destruidor desta classe são
importantes, pois são herdados automaticamente por todas as outras classes. Se você definir uma classe sem
construtor, ainda poderá chamar Create nela, invocando o construtor TObject , que é um método vazio (já
que não há nada para inicializar nesta classe base). Este construtor Create não é virtual e você o substitui
totalmente em suas classes, a menos que esse construtor do-nothing seja bom o suficiente. Chamar o
construtor da classe base é uma boa prática para qualquer subclasse, mesmo que uma chamada direta para
TObject.Create não seja particularmente útil.

note que sublinhei que este é um construtor não virtual porque existe outra classe de biblioteca principal, TCompo-
nent, que define um construtor virtual. O construtor virtual da classe TComponent desempenha uma chave
papel no sistema de streaming, abordado no próximo capítulo.

Para destruir um objeto, a classe TObject possui um método Free (que eventualmente chama o destruidor
Destroy ). Abordei isso detalhadamente no Capítulo 13, juntamente com muitas sugestões sobre o
gerenciamento correto da memória, portanto não há necessidade de reiterá-las aqui.
mais uma vez.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 489

Conhecendo um objeto
Um grupo interessante de métodos da classe TObject são aqueles que retornam informações
sobre o tipo. Os mais comumente usados são os métodos ClassType e ClassName . O método
ClassName retorna uma string com o nome da classe. Por ser um método de classe (como um
grande número de métodos da classe TObject ), você pode aplicá-lo tanto a um objeto quanto a uma
classe. Suponha que você tenha definido uma classe TButton e um Button1
objeto dessa classe. Então as seguintes afirmações têm o mesmo efeito:

Texto := Button1.ClassName;
Texto := TButton.ClassName;

Claro, você também pode aplicá-los a um TObject genérico e não obterá o TObject
informações, mas informações sobre a classe específica do objeto atualmente atribuído a
a variável. Por exemplo, no manipulador de eventos OnClick de um botão, chamando:

Texto := Remetente.ClassName;

provavelmente retornaria o mesmo das linhas acima, que é a string 'TButton'. Isso ocorre porque
o nome da classe é determinado em tempo de execução (pelo próprio objeto específico) e não pelo
compilador (que apenas pensará que se trata de um objeto TObject ). É claro que, se a referência
não for atribuída, ou seja, a variável for nula, a tentativa de chamar qualquer um desses métodos
de classe gerará uma exceção.
Embora obter o nome da classe possa ser útil para depuração, registro e exibição de informações
da classe em geral, geralmente é mais importante acessar a referência de classe da classe. Por
exemplo, é melhor comparar duas referências de classe, que são endereços numéricos
simples, do que as strings com os nomes das classes, que são estruturas de dados e, portanto,
levam mais ciclos de CPU para serem avaliadas. Podemos obter referências de classe com o
método ClassType , enquanto o método ClassParent retorna uma referência de classe para a base
classe da atual, permitindo navegar até a lista de classes-pai, ou seja, classes-base; com o método
retornando nulo para TObject, pois é a classe raiz e, como tal, não possui classe pai. Depois de ter
uma referência de classe, você poderá usá-la para chamar qualquer método de classe, incluindo o
método ClassName .
Outro método muito interessante que retorna informações sobre uma classe é InstanceSize, que
retorna o tamanho de tempo de execução de um objeto, ou seja, a quantidade de memória necessária
para seus campos (e aqueles herdados das classes base). Este é um recurso utilizado internamente
quando o sistema precisa alocar uma nova instância da classe.

note Embora você possa pensar que a função global SizeOf também fornece essas informações, essa função
na verdade, retorna o tamanho de uma referência de objeto – um ponteiro, que invariavelmente tem quatro
ou oito bytes, dependendo da plataforma de destino – em vez do tamanho do próprio objeto. Por
outro lado, InstanceSize retorna o tamanho dos campos, mas essa não é a quantidade real de memória usada pelo
objeto, pois os campos podem ser referências a strings e outros objetos na memória, que requerem
espaço adicional.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

490 - 17: objeto e unidade do sistema

Mais métodos da classe TObject


Existem outros métodos da classe TObject que você pode aplicar a qualquer objeto, e também a qualquer classe
ou referência de classe, porque esses métodos são métodos de classe. Aqui está uma lista parcial, com uma breve
descrição:

· ClassName retorna uma string com o nome da classe, usada principalmente para fins de exibição
poses.

· ClassNameIs verifica o nome da classe em relação a um valor.

· ClassParent retorna uma referência de classe para a classe pai da classe atual ou
classe do objeto. Você pode navegar de ClassParent para ClassParent, até chegar à própria classe TObject ,
ponto em que este método retorna nulo.

· ClassInfo retorna um ponteiro para informações internas de tipo de tempo de execução de baixo nível
(RTTI) da turma. Isso foi usado nos primeiros dias da unidade TypInfo , mas agora foi substituído pelos recursos
da unidade RTTI , conforme abordado no Capítulo 16. Internamente, ainda é assim que o RTTI é buscado
para uma classe.

· ClassType retorna uma referência à classe do objeto (este não é um método de classe e
não pode ser aplicado diretamente a uma classe). Como se trata de uma referência à classe do objeto, duas
classes com o mesmo nome, mas declaradas em unidades diferentes, não corresponderão, o que é correto,
pois Delphi é uma linguagem fortemente tipada.

· InheritsFrom testa se a classe herda (direta ou indiretamente) de um determinado


classe base (isso é muito semelhante ao operador is e, na verdade, como o operador is é
finalmente implementado).

· InstanceSize retorna o tamanho dos dados do objeto em bytes. Esta é uma soma
campos, além de alguns bytes reservados especiais extras (incluindo, por exemplo, a referência de classe,
mas não incluindo a memória real usada por strings e objetos internos).
Observe, mais uma coisa, este é o tamanho da instância, enquanto a referência a uma instância é
apenas enquanto um ponteiro (4 ou 8 bytes, dependendo da plataforma).

· UnitName retorna o nome da unidade na qual a classe está definida, o que pode ser útil para descrever uma
classe. O nome da classe, na verdade, não é único no sistema. Como vimos no capítulo anterior, apenas o
nome da classe qualificada (formado pelo nome da unidade e
o nome da classe, separado por um ponto) é exclusivo em um aplicativo.

· QualifiedClassName retorna esta combinação de unidade e nome de classe, um valor que é


na verdade único em um sistema em execução.

Esses métodos de TObject estão disponíveis para objetos de todas as classes, já que TObject é a classe ancestral
comum de todas as classes Delphi.

Aqui está como podemos usar esses métodos para acessar informações de classe:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 491

procedimento TSenderForm.ShowSender(Sender: TObject);


começar
'Nome da classe:
'
Memo1.Lines.Add( + Sender.ClassName);

se Sender.ClassParent <> nulo então


'Classe pai:
'
Memo1.Lines.Add( + Sender.ClassParent.ClassName);

'Tamanho da instância:
'
Memo1.Lines.Add( + IntToStr(Sender.InstanceSize));

O código verifica se ClassParent é nulo caso você esteja realmente usando um


instância do tipo TObject , que não possui tipo base. Você pode usar outros métodos para realizar testes.
Por exemplo, você pode verificar se o objeto Sender é de um tipo específico com o seguinte código:

se Sender.ClassType = TButton então ...

Você também pode verificar se o parâmetro Sender corresponde a um determinado objeto, com isso
teste:

se Remetente = Button1 então ...

Em vez de verificar uma classe ou objeto específico, geralmente você precisará testar a
compatibilidade de tipo de um objeto com uma determinada classe; isto é, você precisará
verificar se a classe do objeto é uma determinada classe ou uma de suas subclasses. Isso
permite saber se você pode operar no objeto com os métodos definidos para a classe. Este
teste pode ser realizado usando o método InheritsFrom , que também é chamado quando você usa o m
operador, com a única diferença de que o operador is lida com nulo. Os dois testes a seguir são equivalentes:

se Sender.InheritsFrom(TButton) então ...


se Sender for TButton então ...

Mostrando informações da turma


Depois de ter uma referência de classe, você pode adicionar à sua descrição uma lista de todas as suas
classes base. Nos trechos de código a seguir, as classes base de MyClass são adicionadas a um controle
List-Box:

ListParent.Items.Clear;
enquanto MyClass.ClassParent <> nada faz
começar
MinhaClasse := MinhaClasse.ClassParent;
ListParent.Items.Add(MinhaClasse.ClassName);
fim;

Você notará que usamos uma referência de classe no centro do loop while , que testa a ausência de uma
classe pai (nesse caso, a classe atual é TObject). Alternativamente, poderíamos ter escrito a instrução while
de uma das seguintes maneiras:

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

492 - 17: objeto e unidade do sistema

while not MyClass.ClassNameIs(enquanto 'TObject' // ) fazer... // Lento, sujeito a erros


MyClass <> TObject do... Rápido, e legível

Métodos Virtuais do TObject


Embora a estrutura da classe TObject tenha permanecido bastante estável desde os primeiros
dias da linguagem Object Pascal, em determinado momento ela viu a adição de três métodos
virtuais extremamente úteis. Esses são métodos que podem ser chamados em qualquer
objeto, como qualquer outro método TObject , mas a relevância é que são métodos que você
deve substituir e redefinir em suas próprias classes.

note Se você usou o .NET framework, reconhecerá imediatamente que esses métodos fazem parte do Sys-
classe tem.Object da biblioteca de classes base C#. Métodos semelhantes são usados para as classes base
disponíveis em Java e são comumente usados em JavaScript e em outras linguagens. A origem de alguns deles, como
a de toString, pode ser rastreada até Smalltalk, que é considerada a primeira linguagem OOP
medidor.

O método ToString
A função virtual ToString é um placeholder para retornar a representação textual (uma descrição
ou mesmo uma serialização) de um determinado objeto. A implementação padrão do método
na classe TObject retorna o nome da classe:

função TObject.ToString: string;


começar
Resultado := NomeDaClasse;
fim;

Claro, isso está longe de ser útil. Em teoria, cada classe deveria fornecer uma maneira de se descrever
para um usuário, por exemplo, quando um objeto é adicionado a uma lista visual. Algumas das classes
na biblioteca de tempo de execução substituem a função virtual ToString , como TStringBuilder,
TStringWriter e a classe Exception , para retornar as mensagens em uma lista de exceções (conforme
abordado na seção “O mecanismo de exceção interno” do Capítulo 9).
Ter uma forma padrão de retornar a representação de string de qualquer objeto é uma ideia
bastante interessante, e recomendo que você aproveite esse recurso central da classe
TObject , tratando-o como um recurso de linguagem.

note Observe que o método ToString “sobrecarrega semanticamente” a “string de token de análise” ou o símbolo
toString definido na unidade Classes . Por esta razão esse símbolo é referenciado como Classes.toString.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 493

O método igual
A função virtual Equals é um espaço reservado para verificar se dois objetos têm o mesmo valor
lógico, uma operação diferente de verificar se duas variáveis se referem ao mesmo objeto na
memória, algo que você pode conseguir com o operador =. Contudo, e isto é
realmente confuso, a implementação padrão faz exatamente isso, por falta de uma maneira melhor:

função TObject.Equals(Obj: TObject): Boolean;


começar
Resultado:= Obj = Próprio;
fim;

Um exemplo do uso deste método (com um override adequado) está na classe TStrings , na qual o
método Equals compara o número de strings na lista e, se estas forem
iguais, o conteúdo das strings reais é comparado um por um até que um par não corresponda ou
o fim seja alcançado – se o fim for alcançado, as listas serão iguais.
Uma seção da biblioteca, na qual esta técnica é significativamente utilizada, é para suporte a
genéricos, em particular nas unidades Generics.Default e Generics.Collections . É importante para
uma biblioteca ou estrutura definir o conceito de “equivalência de valor” do objeto separadamente da
identidade do objeto. Ter um mecanismo padrão para comparar objetos “por valor” é uma grande
vantagem.

O método GetHashCode

A função virtual GetHashCode é outro espaço reservado emprestado da estrutura .NET para
permitir que cada classe calcule o código hash para seus objetos. O código padrão retorna um
valor aparentemente aleatório, o endereço do próprio objeto:

função TObject.GetHashCode: Inteiro;


começar
Resultado := Inteiro(Self);
fim;

observação Com o endereço dos objetos sendo criados geralmente retirados de um conjunto limitado de áreas de heap, a
distribuição desses números não é uniforme e isso pode afetar adversamente um algoritmo de hash. É altamente
recomendável customizar este método criando um hash baseado em valores lógicos com uma boa distribuição de
hash. Isso levará a um melhor desempenho de dicionários e estruturas de dados que usam valores hash.

A função virtual GetHashCode é usada por algumas classes de coleção que suportam tabelas hash
e como forma de otimizar algum código, como TDictionary<T>.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

494 - 17: objeto e unidade do sistema

Usando métodos virtuais TObject


Aqui está um exemplo baseado em alguns dos métodos virtuais TObject . O exemplo tem uma classe que
substitui dois destes métodos:

tipo
TAnyObject = classe
privado
FValor: Inteiro;
NomeF: string;
público
construtor Create(AName: string; AValue: Integer);
função Equals(Obj: TObject): Boolean; sobrepor;
função ToString: string; sobrepor;
fim;

Na implementação dos três métodos eu simplesmente tive que alterar uma chamada para GetType
com isso para ClassType:

construtor TAnyObject.Create(AName: string; AValue: Integer);


começar
herdado Criar;
FNome := ANome;
FValor := AValor;
fim;

função TAnyObject.Equals(Obj: TObject): Boolean;


começar
Resultado: = (Obj.ClassType = Self.ClassType) e
((Obj como TAnyObject).Value = Self.Value);
fim;

função TAnyObject.ToString: string;


começar
Resultado := Nome;
fim;

Observe que os objetos são considerados iguais se forem exatamente da mesma classe e seus valores
corresponderem, enquanto sua representação de string inclui apenas o campo FName .

O programa cria alguns objetos desta classe ao iniciar:

procedimento TFormSystemObject.FormCreate(Sender: TObject);


começar
Ao1 := TAnyObject.Create(Ao2 := 'Ao1' , 10);
TAnyObject.Create(Ao3 := Ao2; 'Ao2 Ao3'ou , 20);

Ao4 := TAnyObject.Create( 'Mundo 4' , 20);


...

Observe que duas referências (Ao2 e Ao3) apontam para o mesmo objeto na memória, e que o último
objeto (Ao4) possui o mesmo valor numérico.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 495

O programa possui uma interface de usuário que permite ao usuário selecionar quaisquer dois itens e
comparar os objetos selecionados, ambos usando Equals e por comparação direta de referência. Aqui
estão alguns dos resultados:

Comparando Ao1 e Ao4


Igual a: Falso
Referência = Falso

Comparando Ao2 e Ao3


Igual: Verdadeiro
Referência = Verdadeiro

Comparando Ao3 e Ao4


Igual: Verdadeiro
Referência = Falso

O programa possui outro botão usado para testar alguns destes métodos para o próprio botão:

era
Btn2: Botão T;
começar
Btn2 := BtnTest;
'
'É igual a: +
Log(BoolToStr(BtnTest.Equals(Btn2), True));
'Referência = ' +
Log(BoolToStr(BtnTest = Btn2, True));
'
'ObterHashCode: +
Log(IntToStr(BtnTest.GetHashCode));
'
Log(+ BtnTest.ToString);
'Para sequenciar:
fim;

A saída é a seguinte (com um valor hash que muda durante a execução):

Igual: Verdadeiro
Referência = Verdadeiro
ObterHashCode: 28253904
ToString: TButton

Resumo da classe TObject


Resumindo, esta é a interface completa da classe TObject na versão mais recente do
o compilador (com a maior parte do IFDEF e sobrecargas de baixo nível omitidas, juntamente com
seções privadas e protegidas):

tipo
TObject = classe
público
construtor Criar;

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

496 - 17: objeto e unidade do sistema

procedimento
Gratuito; procedimento
DisposeOf; função de classe InitInstance (Instância: Ponteiro): TObject;
procedimento CleanupInstance;

função ClassType: TClass; em linha; função de


classe ClassName: string; função de classe
ClassNameIs (const Nome: string): Boolean; função de classe ClassParent:
TClass; função de classe ClassInfo: Ponteiro;
em linha; função de classe InstanceSize: Integer; em
linha; função de classe InheritsFrom(AClass: TClass):
Boolean; função de classe MethodAddress (const Nome: string): Ponteiro;
função de classe MethodName (Endereço: Ponteiro): string; função de classe
QualifiedClassName: string; função FieldAddress (const Nome: string):
Ponteiro; função GetInterface (const IID: TGUID; out Obj):
Boolean;

função de classe GetInterfaceEntry(


const IID: TGUID): PInterfaceEntry; função de
classe GetInterfaceTable: PInterfaceTable; função de classe
UnitName: string; função de classe
UnitScope: string;

função Equals(Obj: TObject): Boolean; virtual; função


GetHashCode: Inteiro; virtual; função ToString: string;
virtual; função SafeCallException(ExceptObject:
TObject;
ExceptAddr: Ponteiro): HResult; virtual;
procedimento Pós-Construção; virtual; procedimento
AntesDestruição; virtual; procedimento
Despacho(var Mensagem); virtual; procedimento
DefaultHandler(var Mensagem); virtual; função de classe
NewInstance: TObject; virtual; procedimento FreeInstance;
virtual; destruidor Destruir; virtual;

propriedade pública Disposed: Boolean read GetDisposed; fim;

Unicode e nomes de classes


Métodos sobrecarregados como MethodAddress e FieldAddress podem usar uma string Unicode (UTF-16,
como de costume) ou um parâmetro ShortString que é tratado como uma string UTF-8.
Na verdade, as versões que usam uma string Unicode normal as convertem chamando a função
UTF8EncodeToShortString:

função TObject.FieldAddress (const Nome: string): Ponteiro; resultado inicial: =

FieldAddress (UTF8EncodeToShortString (Nome));

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 497

fim;

Desde que o suporte Unicode foi introduzido na linguagem, os nomes das classes em Object Pas-cal
usam internamente a representação ShortString (uma matriz de caracteres de um byte), mas
com uma codificação UTF-8 em vez da codificação ANSI tradicional do ShortString
tipo. Isto acontece tanto no nível TObject quanto no nível RTTI.

Por exemplo, o método ClassName é implementado com algum código de nível realmente baixo, como
segue:

função de classe TObject.ClassName: string;


começar
Resultado:= UTF8ToString(
PShortString(PPointer(
Integer(Self) + vmtClassName)^)^);
fim;

Da mesma forma, na unidade TypInfo , todas as funções que acessam nomes de classes convertem as
representações internas UTF-8 ShortString em UnicodeString. Algo semelhante acontece com nomes de
propriedades.

A Unidade do Sistema
Embora a classe TObject tenha claramente um papel fundamental para a linguagem, tornando muito difícil
dizer se faz parte da linguagem ou da biblioteca de tempo de execução, existem outras classes de baixo
nível na unidade System que constituem uma parte fundamental e integrada do suporte do compilador.

A maior parte do conteúdo desta unidade, entretanto, é composta de estruturas de dados de baixo
nível, estruturas de registros simples, funções e procedimentos e algumas classes.

Aqui vou me concentrar principalmente nas classes, mas é inegável que muitos outros recursos da
unidade System são fundamentais para a linguagem. Por exemplo, a unidade do sistema define as
chamadas funções “intrínsecas” que não possuem código real, mas são resolvidas diretamente pelo
compilador. Um exemplo é SizeOf, uma chamada que o compilador substitui diretamente pelo
tamanho real da estrutura de dados que foi passada como parâmetro.

Você pode ter uma ideia da função especial da unidade do sistema lendo o comentário adicionado ao
seu início (principalmente para explicar por que navegar pelos símbolos do sistema leva a esta
unidade... mas não ao símbolo que você estava procurando):

{Constantes, tipos, procedimentos predefinidos,}


{ e funções (como } como True, Integer, {Writeln) ou
não
possuem declarações reais.}
{Em vez disso, eles são} são incorporados ao compilador se foram declarados
{e são tratados} como

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

498 - 17: objeto e unidade do sistema

{ no o início da unidade do sistema. }

Ler o código-fonte desta unidade pode ser um tanto tedioso, também porque aqui você pode encontrar
alguns dos códigos de nível inferior de toda a biblioteca de tempo de execução. Então decidi descrever
apenas uma seleção muito limitada de seu conteúdo.

Tipos de sistema selecionados


Como mencionado acima, a unidade System define os principais tipos de dados e muitos aliases de tipos para
diferentes tipos numéricos, outros tipos ordinais e strings. Existem outros tipos de dados principais
(incluindo enumerações, registros e aliases de tipo forte) usados no nível mais baixo pelo sistema, que vale
a pena dar uma olhada:

· TVisibilityClasses é uma enumeração usada para configurações de visibilidade RTTI (veja o Capítulo 16
para mais detalhes)

· TGUID é um registro usado para representar um GUID no Windows, mas também em todos os outros
sistemas operacionais suportados

· TMethod é um registro central que representa a estrutura usada para o manipulador de eventos, com um
ponteiro para um endereço de método e outro para um objeto atual (mencionado brevemente no Capítulo
10)

· TMonitor é um registro que implementa um mecanismo de sincronização de threads (chamado “monitor”)


inventado por CAR Hoare e Per Brinch Hansen e detalhado na Wikipedia no artigo “Monitor
(synchronization)”. Este é um recurso central de suporte a threading da própria linguagem, já que as
informações do TMonitor são anexadas a qualquer
objeto no sistema.

· TDateTime é um alias fortemente digitado do tipo Double que é usado para armazenar datas
informação (na parte integral do valor) e informação de tempo (na parte decimal). Outros aliases incluem
os tipos TDate e TTime. Esses tipos foram abordados no Capítulo 2.

· THandle é um alias de tipos numéricos, usado para representar uma referência a um objeto do sistema
operacional, geralmente chamado de “handle” (pelo menos no jargão da API do Windows).

· TMemoryManagerEx é um registro que contém as operações principais da memória necessárias para


substitua o gerenciador de memória do sistema por um personalizado (esta é a versão mais recente do
TMemoryManager que ainda está disponível para compatibilidade com versões anteriores).

· THeapStatus é um registro com informações sobre o status da memória heap, brevemente mencionado
no Capítulo 13.

· TTextLineBreakStyle é uma enumeração que indica o estilo de quebra de linha para arquivos de texto em um
determinado sistema operacional. A variável global DefaultTextLineBreakStyle desse tipo contém as
informações atuais, usadas por muitas bibliotecas do sistema. Da mesma forma o

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

17: objeto e unidade do sistema - 499

A constante sLineBreak (estranhamente em minúsculas no código da biblioteca) expressa as


mesmas informações que um valor de string.

Interfaces na unidade do sistema


Existem vários tipos de interface (e algumas classes que implementam interfaces no nível central) que fazem
parte da unidade System e vale a pena examinar. Aqui estão os tipos mais relevantes relacionados à interface
na unidade do sistema :

IInterface é o tipo de interface básico do qual todas as outras interfaces herdam e tem o mesmo
papel fundamental que o TObject tem para as classes.

· IInvokable e IDispatch são interfaces que suportam formas de invocação dinâmica


(parcialmente vinculado à implementação do Windows COM)

· As operações de suporte e comparação do enumerador são definidas pelas seguintes interfaces: IEnumerator,
IEnumerable, IEnumerator<T>, IEnumerable<T>, IComparable, IComparable<T> e IEquatable<T>.

Existem também algumas classes principais que oferecem uma implementação básica de interfaces. Muitas
vezes você herda dessas classes base ao implementar uma interface, conforme abordado no Capítulo 11:

· TInterfacedObject, uma classe que possui uma implementação básica de contagem de referências e
verificação de ID de interface

· TAggregatedObject e TContainedObject, duas classes que oferecem implementação especial para objetos
agregados e implementa a sintaxe

Rotinas de sistema selecionadas


O número de procedimentos e funções intrínsecos e padrão na unidade do Sistema é bastante grande, mas
a maioria deles não é comumente usada. Aqui está uma seleção muito limitada de funções e procedimentos
básicos que todo desenvolvedor Object Pascal deve conhecer:

· Mover é a operação principal de cópia de memória no sistema, apenas copiando um determinado número de
bytes de um local de memória para outro (muito poderoso, muito rápido, mas um pouco perigoso)

· As funções ParamCount e ParamStr podem ser usadas para processar parâmetros de linha de comando de
uma aplicação (e também funcionam em sistemas GUI como Windows e Mac).

· Random e Randomize são duas funções clássicas (provavelmente derivadas do BASIC) que fornecem
valores aleatórios (pseudo-aleatórios, mas apenas se você se lembrar de chamar
Randomize, caso contrário você obterá a mesma sequência toda vez que executar seu programa)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

500 - 17: objeto e unidade do sistema

· Um número significativo de funções matemáticas básicas, totalmente omitidas aqui

· Muitas funções de processamento e conversão de strings (entre UTF-16 Unicode,


UTF-8, ANSI e outros formatos de string), alguns dos quais são específicos da plataforma

note Algumas dessas funções têm uma definição indireta. Em outras palavras, a função é na verdade um ponteiro para a função
real, de modo que o comportamento original do sistema possa ser substituído dinamicamente no código em tempo
de execução. (Se você sabe o que está fazendo, é claro, pois essa pode ser uma boa maneira de jogar fora a
memória do seu aplicativo).

Atributos RTTI predefinidos

O último grupo de tipos de dados, que quero mencionar neste capítulo, está relacionado aos
atributos – as informações extras de RTTI que você pode anexar a qualquer símbolo da linguagem.
Este tópico foi abordado no Capítulo 16, mas não mencionei os atributos predefinidos no sistema.

Aqui estão algumas das classes de atributos definidas na unidade do sistema :

· TCustomAttribute é a classe base para todos os atributos personalizados. Esta é a classe base
você precisa herdar atributos de (e é a única maneira de uma classe ser identificada pelo compilador
como sendo um atributo, pois não há sintaxe de declaração especial).
· WeakAttribute é usado para indicar referências fracas para referências de interface (veja
Capítulo 13)

· UnsafeAttribute é usado para desabilitar a contagem de referências para referências de interface (também
abordado no Capítulo 13)

· RefAttribute é usado para qualificar parâmetros de função constantes

· VolatileAttribute indica variáveis voláteis que podem ser modificadas externamente e não devem ser
otimizadas pelo compilador

· StoredAttribute é uma forma alternativa de expressar o sinalizador armazenado de uma propriedade

· HPPGENAttribute controla a geração do arquivo de interface C++ (HPP)

· HFAAttribute pode ser usado para ajustar o mecanismo de passagem de parâmetros de CPU ARM de 64
bits, controlando os agregados homogêneos de ponto flutuante (HFA)

Há mais na unidade Sistema , mas isso é para desenvolvedores especialistas. Prefiro passar para o
último capítulo, onde abordo a unidade Classes e alguns dos recursos da biblioteca em tempo de execução.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 501

18: outro rtl principal


Aulas

Se a classe TObject e a unidade System podem ser consideradas como sendo uma parte estrutural da
linguagem, algo necessário ao próprio compilador para construir qualquer aplicação, todo o resto na
biblioteca de tempo de execução (RTL) pode ser considerado como opcional. extensões para o sistema
central.

O RTL possui uma coleção muito grande de funções de sistema, abrangendo as operações padrão mais
comuns, que remontam parcialmente à época do Turbo Pascal e são anteriores à linguagem Object Pascal.
Muitas unidades do RTL são coleções de funções e rotinas, incluindo utilitários básicos (SysUtils), funções
matemáticas (Math), operações de string (StringUtils), processamento de data e hora (DateUtils) e
muitos outros.

Neste livro eu realmente não quero me aprofundar nessa parte mais tradicional do RTL, mas sim focar nas
classes principais, que são a base das bibliotecas de componentes visuais usadas no Object Pascal
(VCL e FireMonkey) e também de outros subsistemas. A classe TCom-ponent , por exemplo, define o
conceito de arquitetura “baseada em componentes”. Também é fundamental para gerenciamento de memória
e outros recursos básicos. O TPersistente
class é a chave para streaming de representações de componentes.

Existem muitas outras classes que poderíamos observar, já que o RTL é extremamente grande e
abrange o sistema de arquivos, o suporte de threading central, a biblioteca de programação paralela,
construção de strings, muitos tipos diferentes de coleções e classes de contêineres, classes principais

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

502 - 18: outras classes principais de rtl

estruturas geométricas (como pontos e retângulos), estruturas matemáticas básicas (como vetores e
matrizes) e muito, muito mais.

Dado que o foco do livro é realmente a linguagem Object Pascal, e não um guia para as bibliotecas, aqui
vou me concentrar apenas em algumas classes selecionadas, escolhidas por seu papel principal ou
porque foram introduzidas nos últimos anos e são amplamente ignorados
pelos desenvolvedores.

A Unidade de Aulas
A unidade na base da biblioteca de classes Object Pascal RTL (e também das bibliotecas visuais) é
apropriadamente chamada de System.Classes. Esta unidade contém uma grande coleção de aulas em
sua maioria variadas, sem um foco específico. Vale a pena dar uma breve olhada no
importantes e acompanhar com uma análise aprofundada dos mais importantes.

As aulas na unidade de aulas


Aqui está a pequena lista de aproximadamente metade das classes realmente definidas em todo o Classes
unidade:

· TList é uma lista básica de ponteiros que geralmente é adaptada como uma lista não digitada. Em geral
é recomendado usar TList<T> , conforme abordado no Capítulo 14.

· TInterfaceList é uma lista thread-safe de interfaces que implementam IInterfaceList,


vale a pena dar uma segunda olhada (mas não abordado aqui).

· TBits é uma classe muito simples para manipular bits individuais em um número ou algum outro valor.
É um nível muito mais alto do que fazer manipulação de bits com turnos e operadores binários
ou e e .
· TPersistent é uma classe fundamental (a classe base de TComponent), abordada em detalhes na próxima
seção.

· TCollectionItem e TCollection são classes utilizadas para definir propriedades de coleção, ou seja,
propriedades com um array de valores. Estas são classes importantes para desenvolvedores de
componentes (e indiretamente ao usar componentes), e não tanto para desenvolvedores de
aplicações genéricas.

· TStrings é uma lista abstrata de strings, enquanto TStringList é uma implementação real da classe
base TStrings , fornecendo armazenamento para as strings reais. Cada item também possui um
objeto anexado, e essa é a maneira padrão de usar listas de strings para pares de strings nome/
valor. Há mais informações sobre esta classe na seção “Usando listas de strings” no final deste
capítulo.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 503

· TStream é uma classe abstrata que representa qualquer sequência de bytes com
acesso, que pode abranger muitas opções de armazenamento diferentes (memória, arquivos, strings,
soquetes, campos BLOB e muitos outros). A unidade Classes define muitas das classes de fluxo
específicas, incluindo THandleStream, TFileStream, TCustomMemoryStream, TMemoryStream,
TBytesStream, TStringStream e TResourceStream. Outros fluxos específicos são declarados em
diferentes unidades RTL. Você pode ler uma introdução aos streams na seção “Apresentando
Streams” deste capítulo.

· Classes para streaming de componentes de baixo nível, como TFiler, TReader, TWriter e
TParser, usado principalmente por autores de componentes, e mesmo assim não com tanta frequência por eles.

· Classe TThread , que define suporte para multi-threaded independente de plataforma


formulários. Também existe uma classe para operações assíncronas, chamada TBaseAsyncRe-sult.

· Classes para a implementação do padrão observador (usado, por exemplo, em ligações visuais ao vivo),
incluindo TObservers, TLinkObservers e TObserverMapping.

· Classes para atributos personalizados específicos como DefaultAttribute, NoDefaultAttribute,


StoredAttribute e ObservableMemberAttribute.
· A classe fundamental TComponent , a classe base de todos os componentes visuais e não visuais tanto no VCL
quanto no FireMonkey, abordada em detalhes posteriormente neste capítulo.

· Suporte a classes para ações e listas de ações (ações são abstrações de “comandos” emitidos por elementos da UI ou
internamente), incluindo TBasicAction e TBasicAction-Link.

· A classe que representa um contêiner de componente não visual: TDataModule.

· Interfaces de nível superior para operações de arquivos e fluxos, incluindo TTextReader e TTextWriter,
TBinaryReader e TBinaryWriter, TStringReader e TString-Writer, TStreamReader e TStreamWriter.
Essas classes também são abordadas neste capítulo.

A classe TPersistent
A classe TObject possui uma subclasse muito importante, uma das bases de toda a biblioteca, chamada
TPersistent. Se você observar os métodos da classe, sua importância pode ser surpreendente, pois a classe
faz muito pouco. Um dos elementos-chave da classe TPer-sistent é que ela é definida com a opção especial do
compilador {M+}, cuja função é
habilite a palavra-chave publicada , abordada no Capítulo 10.

A palavra-chave publicada tem papel fundamental para propriedades de streaming, e isso


explica o nome da classe. Somente classes herdadas de TPersistent podem ser usadas como
o tipo de dados das propriedades publicadas. Apesar do fato de que a extensão do RTTI em versões posteriores
do compilador Object Pascal permitiria um modelo diferente, o VCL e

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

504 - 18: outras classes principais de rtl

O streaming dos componentes das bibliotecas FMX permanece baseado no papel da palavra-chave
publicada e na opção do compilador {$M+} .

note Usando o compilador atual, se você adicionar a palavra-chave publicada a uma classe que não herda de TPer-sistent
e não possui o sinalizador do compilador {$M+} , o sistema adicionará o suporte adequado de qualquer
maneira, indicando isso com um aviso .

Qual é o papel específico da classe TPersistent na hierarquia? Primeiro, serve como


classe base de TComponent, que apresentarei na próxima seção. Em segundo lugar, ele é usado
como classe base para tipos de dados usados para valores de propriedades, para que essas propriedades
e sua estrutura interna possam ser transmitidas adequadamente. Exemplos são classes que representam
listas de strings, bitmaps, fontes e outros objetos.

Se a característica mais relevante da classe TPersistent é a sua “ativação” do tratamento da seção


publicada de uma subclasse, ela ainda possui alguns métodos interessantes que vale a pena examinar.
O primeiro é o método Assign , que é usado para fazer uma cópia dos dados do objeto de uma
instância para outra (uma cópia profunda, não uma cópia das referências). Este é um recurso que cada
classe persistente usada para valores de propriedade deve implementar manualmente (já que não há
operação automática de cópia profunda na linguagem). A segunda é a operação inversa, AssignTo,
que é protegida. Esses dois métodos, e os outros poucos disponíveis na classe, são usados
principalmente por criadores de componentes, e não por desenvolvedores de aplicativos.

A classe TComponent
A classe TComponent é a base das bibliotecas de componentes que são mais frequentemente
usado em conjunto com compiladores Object Pascal. O conceito de um componente é basicamente o
de uma classe que possui algum comportamento extra em tempo de design, recursos específicos
de streaming (para que a configuração em tempo de design possa ser salva e restaurada em um
aplicativo em execução) e o PME ( modelo de propriedade-método-evento) que discutimos no
Capítulo 10.

Esta classe define um número significativo de comportamentos e recursos padrão, introduz seu próprio
modelo de memória baseado em um conceito de propriedade de objetos, notificações de
componentes cruzados e muito mais. Embora não seja feita uma análise completa de todas as
propriedades e métodos, certamente vale a pena focar em alguns dos principais recursos da
classe TComponent por seu papel central no RTL.

Outro recurso crítico da classe TComponent é o fato de ela introduzir um construtor virtual Cre-ate ,
crítico para a capacidade de criar um objeto a partir de uma referência de classe enquanto ainda
invoca o código construtor específico da classe. Nós tocamos nisso no Capítulo 12, mas esta é
uma característica peculiar da linguagem Object Pascal, que vale a pena entender.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 505

Propriedade dos Componentes


O mecanismo de propriedade é um elemento-chave da classe TComponent . Se um componente for criado
com um componente proprietário (passado como parâmetro para seu construtor virtual), este componente
proprietário torna-se responsável por destruir o componente de propriedade. Resumindo, cada componente
tem uma referência ao seu proprietário (a propriedade Owner ), mas também uma lista de componentes
que possui (a propriedade do array Components ) e seu número (a propriedade Compo-nentCount ).

Por padrão, quando você descarta um componente em um designer (um formulário, um quadro ou um
módulo de dados), este é considerado o proprietário do componente. Ao criar um componente em
código, cabe a você especificar um proprietário ou passar nulo; nesse caso, você será
responsável por liberar você mesmo o componente da memória.

Você pode usar as propriedades Components e ComponentCount para listar os componentes pertencentes
a um componente (AComp neste caso), com código como:

era
Eu: Inteiro;
começar
para I := 0 para AComp.ComponentCount – 1 faça
AComp.Componentes[I]. Faça alguma coisa;

Ou use o suporte de enumeração nativo, escrevendo:

era
ChildComp: TComponent;
começar
para ChildComp em AComp do
ChildComp. Faça alguma coisa;

Quando um componente é destruído, ele é removido da lista de proprietários (se houver) e destrói todos
os componentes que possui. Esse mecanismo é crucial para o gerenciamento de memória em Object Pascal:
como não há coleta de lixo, a propriedade pode resolver a maioria dos problemas de gerenciamento de
memória, como vimos parcialmente no Capítulo 13.

Como mencionei, geralmente todos os componentes de um formulário ou módulo de dados têm o


formulário ou módulo de dados como proprietário. Contanto que você libere o formulário ou módulo de
dados, os componentes que eles hospedam também serão destruídos. Isto é o que acontece quando
componentes são criados a partir de um fluxo.

Propriedades do Componente
Além do mecanismo principal de propriedade (que também inclui notificações e outros recursos não abordados
aqui), qualquer componente possui duas propriedades publicadas:

· Nome é uma string com o nome do componente. Isto é usado para encontrar um componente dinamicamente
(chamando o método FindComponent do proprietário) e para conectar o componente com o
campo do formulário referente a ele. Todos os componentes pertencentes ao

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

506 - 18: outras classes principais de rtl

o mesmo proprietário deve ter nomes exclusivos que não diferenciam maiúsculas de minúsculas, mas seus nomes
também podem estar vazios. Duas regras curtas aqui: defina nomes adequados de componentes para melhorar
a legibilidade do código e nunca altere o nome de um componente em tempo de execução (a menos que você
esteja realmente ciente dos possíveis efeitos colaterais desagradáveis).

· Tag é um valor NativeInt (antigamente era um Integer) não utilizado pela biblioteca, mas disponível para você conectar
informações extras ao componente. O tipo é compatível com o tamanho de ponteiros e referências de objetos,
que geralmente são armazenados no Tag de um componente.

Streaming de componentes
O mecanismo de streaming, usado tanto pelo FireMonkey quanto pelo VCL para criar arquivos FMX ou DFM, é baseado
na classe TComponent . O mecanismo de streaming do Delphi salva o
propriedades e eventos publicados de um componente e seus subcomponentes. Essa é a representação que você
obtém em um arquivo DFM ou FMX, e também o que você obtém se copiar e colar um componente do designer em
um editor de texto.

Existem métodos para obter as mesmas informações em tempo de execução, incluindo os métodos WriteComponent e
ReadComponent da classe TStream , mas também os métodos ReadComponentRes e
WriteComponentRes da mesma classe, e os métodos ReadRootComponent e WriteRootComponent das
classes TReader e TWriter – classes especiais que ajudam a lidar com com streaming de componentes. Essas
operações geralmente usam a representação binária de fluxos de formulário: Você pode usar o procedimento global
ObjectResourceToText para converter a representação binária do formulário em textual e
ObjectTextToResource para a conversão reversa.

Um elemento chave é que o streaming não é um conjunto completo das propriedades publicadas de um componente. A
transmissão inclui:

· As propriedades publicadas de um componente com valor diferente do valor padrão (ou seja, os valores padrão não
são salvos para reduzir o tamanho).

· Somente imóveis publicados marcados como armazenados (que é o padrão). Uma propriedade com
armazenado definido como False (ou uma função retornando False), não será salvo.

· Entradas adicionais que não correspondam às propriedades do componente podem ser adicionadas em execução
tempo substituindo o método DefineProperties .

Quando um componente é criado a partir do arquivo de fluxo, ocorre a seguinte sequência:

· O construtor virtual Create do componente é chamado (executando o comando inicial apropriado).


código de ização)

· As propriedades e eventos são carregados do stream (no caso de eventos, remapeamento


o nome do método para o endereço real do método na memória)

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 507

· O método virtual Loaded é chamado para finalizar o carregamento (e os componentes podem fazer
processamento personalizado extra, desta vez com os valores das propriedades já carregados do
stream)

Acesso moderno a arquivos

Tomando emprestado de seu ancestral da linguagem Pascal, o Object Pascal ainda possui palavras-chave
e mecanismos básicos de linguagem para processar arquivos. Eles estavam basicamente obsoletos
quando o Object Pascal foi introduzido e não vou abordá-los neste livro. O que abordarei nesta seção,
em vez disso, são algumas técnicas modernas para processamento de arquivos, apresentando a
unidade IOUtils , as classes de fluxo e as classes de leitor e gravador.

A Unidade de Utilitários de Entrada/Saída


A unidade System.IOUtils é uma adição relativamente recente à Run Time Library. Ele define três
registros principalmente de métodos de classe: TDirectory, TPath e TFile. Embora seja bastante óbvio que
o TDirectory serve para navegar em pastas e encontrar seus arquivos e subpastas, pode não estar tão
claro qual é a diferença entre um TPath e um TFile. O primeiro, TPath, é usado para manipular nomes de
arquivos e diretórios, com métodos para extrair a unidade, nome de arquivo sem caminho, extensão e
similares, mas também para manipular caminhos UNC. O registro TFile , por outro lado, permite verificar
os carimbos de data/hora e atributos do arquivo, mas também permite manipular um arquivo, gravando
nele ou copiando-o. Como sempre, pode valer a pena olhar um exemplo. O exemplo IoFilesInFolder pode
extrair
todas as subpastas de uma determinada pasta e pode capturar todos os arquivos com uma determinada
extensão disponível nessa pasta.

Extraindo Subpastas
O programa pode preencher um list box com as subpastas encontradas em uma pasta definida pelo
usuário, utilizando o método GetDirectories do registro TDirectory , passando como parâmetro o
valor TSearchOption.soAllDirectories. O resultado é um array de strings que você pode
enumerar:

procedimento TFormIoFiles.BtnSubfoldersClick(Sender: TObject);


era
PathList: TStringDynArray;
StrPath: string;
começar
se TDirectory.Exists(EdBaseFolder.Text) então

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

508 - 18: outras classes principais de rtl

começar
ListBox1.Items.Clear;
PathList: = TDirectory.GetDirectories (EdBaseFolder.Text,
TSearchOption.soAllDirectories, nil);
para StrPath em PathList faça
ListBox1.Items.Add(StrPath);
fim;
fim;

Pesquisando arquivos

Um segundo botão do programa permite listar todos os arquivos PAS nessas pastas, examinando cada
pasta com uma chamada GetFiles baseada em uma determinada máscara. Você pode ter uma
filtragem mais complexa passando um método anônimo do tipo TFilterPredicate para uma versão
sobrecarregada de GetFiles.

Este exemplo usa a filtragem mais simples baseada em máscara e preenche uma lista de strings interna.
Os elementos desta lista de strings são então copiados para a interface do usuário após a remoção do
caminho completo, mantendo apenas o nome do arquivo.

Ao chamar o método GetDirectories você obtém apenas as subpastas, mas não a atual. É por isso que o programa
pesquisa primeiro na pasta atual e depois examina cada subpasta:

procedimento TFormIoFiles.BtnPasFilesClick(Sender: TObject);

era
PathList, FilesList: TStringDynArray;
StrPath, StrFile: string;
começar
se TDirectory.Exists(EdBaseFolder.Text) então
começar
// Limpar
ListBox1.Items.Clear;

// Procure em a pasta fornecida


FilesList: = TDirectory.GetFiles (EdBaseFolder.Text, para StrFile em FilesList do '*.não' );

SFilesList.Add(StrFile);

// Procure em todos subpastas


PathList: = TDirectory.GetDirectories (EdBaseFolder.Text,
TSearchOption.soAllDirectories, nil);
para StrPath em PathList faça
começar
FilesList: = TDirectory.GetFiles (StrPath, para StrFile em FilesList '*.não' );
do
SFilesList.Add(StrFile);
fim;

Agora
// apenas copie dos arquivos (sem caminho)
os nomes para a caixa de listagem
para StrFile em SFilesList faça
ListBox1.Items.Add(TPath.GetFileName(StrFile));

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 509

fim;
fim;

Nas linhas finais, a função GetFileName do TPath é usada para extrair o nome do arquivo do caminho completo
do arquivo. O registro TPath possui alguns outros métodos interessantes, incluindo GetTempFileName,
GetRandomFileName , um método para mesclar caminhos, alguns
para verificar se são válidos ou contêm caracteres ilegais e muito mais.

Apresentando fluxos
Se a unidade IOUtils for para localizar e manipular arquivos, quando você quiser ler ou gravar um arquivo (ou
qualquer outra estrutura de dados semelhante acessada sequencialmente), você poderá usar a classe TStream e
suas muitas classes descendentes. A classe abstrata TStream possui apenas algumas propriedades (tamanho e
posição), juntamente com a interface básica que todas as classes de fluxo compartilham com os principais
métodos de leitura e gravação . O conceito expresso por esta classe é o acesso sequencial. Cada vez que você
lê e escreve um número de bytes, a posição atual é avançada por esse número. Para a maioria dos fluxos, você
pode mover a posição para trás, mas também pode haver fluxos unidirecionais.

Classes de fluxo comuns


Como mencionei anteriormente, a unidade Classes define diversas classes de fluxo concretas, incluindo as seguintes:

· THandleStream define um fluxo de arquivo em disco referenciado com um identificador de arquivo.

· TFileStream define um fluxo de arquivos em disco referenciado por um nome de arquivo.

· TBufferedFileStream é um fluxo de arquivos de disco otimizado que usa um buffer de memória para desempenho
extra. Esta classe de stream foi introduzida no Delphi 10.1.

· TMemoryStream é um fluxo de dados na memória que você pode acessar com um ponteiro.

· TBytesStream representa um fluxo de bytes na memória, que você também pode acessar
como uma matriz de bytes.

· TStringStream associa um stream a uma string na memória.


· TResourceStream define um fluxo que pode ler dados de recursos vinculados ao exe-
arquivo cortável de um aplicativo.

· TPointerStream permite ler e escrever qualquer bloco de dados na memória usando o


Interface TStream , indicando a localização do ponteiro e o tamanho do bloco de memória. Esta
classe foi adicionada no Delphi 11.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

510 - 18: outras classes principais de rtl

Usando fluxos
Criar e usar um fluxo pode ser tão simples quanto criar uma variável de um tipo específico e chamar os
métodos de um componente para carregar o conteúdo do arquivo. Por exemplo, dado um fluxo e um
componente de memorando, você pode escrever:

AStream := TFileStream.Create(FileName, fmOpenRead);


Memo1.Lines.LoadFromStream(AStream);

Como você pode ver neste código, o método Create para fluxos de arquivos possui dois parâmetros: o
nome do arquivo e algum sinalizador indicando o modo de acesso solicitado. Como mencionei, os fluxos
suportam operações de leitura e gravação, mas são de nível bastante baixo, então recomendo usar as
classes de leitor e gravador discutidas na próxima seção. Em vez disso, o que o uso direto de stream
fornece é um conjunto de operações abrangentes, como carregar um stream inteiro no trecho de código
acima ou copiar um em outro, como neste exemplo:

procedimento CopyFile(SourceName, TargetName: String);


era
Stream1, Stream2: TFileStream;
começar
Stream1 := TFileStream.Create(SourceName, fmOpenRead);
tentar
Stream2 := TFileStream.Create(TargetName,
fmOpenWrite ou fmCreate);
tentar
Stream2.CopyFrom(Stream1, Stream1.Size);
finalmente
Stream2.Free;
fim
finalmente
Stream1.Grátis;
fim
fim;

Usando leitores e escritores


Uma abordagem muito boa para escrever e ler fluxos é usar as classes leitor e escritor que fazem parte
do RTL. Existem seis classes de leitura e escrita, definidas na unidade Classes :

• TStringReader e TStringWriter trabalham em uma string na memória (diretamente ou


usando um TStringBuilder)

• TStreamReader e TStreamWriter funcionam em um fluxo genérico (um fluxo de arquivo, um


fluxo de memória e outras classes derivadas de TStream)

• TBinaryReader e TBinaryWriter funcionam com dados binários e não com texto

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 511

Cada leitor de texto implementa algumas técnicas básicas de leitura:

função Leitura: Inteiro; sobrecarga;


função ReadLine: string;
função ReadToEnd: string;

Cada escritor de texto possui dois conjuntos de operações sobrecarregadas sem (Write) e com
(WriteLine) um separador de fim de linha. Aqui está o primeiro conjunto:

procedimento Write(Valor: Boolean); sobrecarga;


procedimento Write(Valor: Char); sobrecarga;
procedimento Write(const Valor: TCharArray); sobrecarga;
procedimento Write(Valor: Duplo); sobrecarga;
procedimento Write(Valor: Inteiro); sobrecarga;
procedimento Write(Valor: Int64); sobrecarga;
procedimento Write(Valor: TObject); sobrecarga;
procedimento Write(Valor: Único); sobrecarga;
procedimento Write (const Valor: string); sobrecarga;
procedimento Write(Valor: Cardinal); sobrecarga;
procedimento Write(Valor: UInt64); sobrecarga;
procedimento Write (formato const: string; Args: array de const); sobrecarga;
procedimento Write(Valor: TCharArray; Índice, Contagem: Inteiro); sobrecarga;

Leitores e escritores de texto


Para gravar em um stream, a classe TStreamWriter usa um stream ou cria um usando o
nome do arquivo, um atributo anexar/criar e a codificação Unicode passada como parâmetro
termos.

Então podemos escrever, como fiz no exemplo do ReaderWriter :


era
Sw: TStreamWriter;
começar
Sw := TStreamWriter.Create(False, 'teste.txt' ,
TEncoding.UTF8);
tentar
'Olá Mundo' );
'Tenha um
a bom dia' );
Sw.WriteLine(Sw.WriteLine(Sw.WriteLine(Esquerda);
finalmente
Sw.Livre;
fim;

Para ler o TStreamReader, você pode trabalhar novamente em um stream ou arquivo (nesse caso ele
pode detectar a codificação do marcador UTF BOM):
era
RS: TStreamReader;
começar
SR := TStreamReader.Create(tente 'teste.txt' , Verdadeiro);

enquanto não SR.EndOfStream faça

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

512 - 18: outras classes principais de rtl

Memo1.Lines.Add(SR.ReadLine);
finalmente
SR.Livre;
fim;

Observe como você pode verificar o status EndOfStream . Comparadas ao uso direto de fluxos de texto (ou mesmo
strings), essas classes são particularmente úteis de usar e fornecem bom desempenho.

Leitor e escritor binário


As classes TBinaryReader e TBinaryWriter destinam-se ao gerenciamento de dados binários em vez de arquivos de
texto. Essas classes geralmente encapsulam um fluxo (um fluxo de arquivo ou qualquer tipo de fluxo na memória, incluindo
soquetes e campos BLOB de banco de dados) e têm métodos de leitura e gravação sobrecarregados .

Como exemplo (bastante simples), escrevi o programa BinaryFiles . Na primeira parte, este programa grava alguns
elementos binários em um arquivo (o valor de uma propriedade e a hora atual) e os lê de volta, atribuindo o valor da
propriedade:

procedimento TFormBinary.BtnWriteClick(Sender: TObject);


era
PN: TBinaryWriter;
começar
BW := TBinaryWriter.Create(tente 'dados de teste' , Falso);

BW.Write(Esquerda);
BW.Escrever(Agora);
'
'Tamanho do arquivo: + IntToStr(BW.BaseStream.Size));
Registro (finalmente
BW.Livre;
fim;
fim;

procedimento TFormBinary.BtnReadClick(Remetente: TObject);


era
BR: TBinaryReader;
ATime: TDateTime;
começar
BR := TBinaryReader.Create(tente 'dados de teste');

Esquerda:= BR.ReadInt32;
'
'Leia à esquerda:
Log(+ IntToStr(Esquerda));
ATime := BR.ReadDouble;
'
'Tempo de leitura:
Log(+ TimeToStr(ATime));
finalmente
BR.Grátis;
fim;
fim;

A regra principal no uso dessas classes de leitor e gravador é que você deve ler os dados em
na mesma ordem em que você escreveu, caso contrário você bagunçará totalmente os dados. Na verdade, apenas o

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 513

os dados binários de campos individuais são salvos, sem nenhuma informação sobre o campo em si.
Porém, nada impede você de interpor dados e metadados no arquivo, como salvar o tamanho do próximo
tipo de dados antes de gravar os dados para esse tipo.

Construindo Strings e Listas de Strings


Depois de dar uma olhada nos arquivos e fluxos, quero dedicar um pouco de tempo focando em maneiras
de manipular strings e listas de strings. Estas são operações muito comuns e há um rico conjunto de
recursos RTL focados nelas. Aqui vou apresentar apenas alguns.

A classe TStringBuilder
Já mencionei no Capítulo 6 que, diferentemente de outras linguagens, Object Pascal tem suporte completo
para concatenação direta de strings, que na verdade é uma operação bastante rápida.
A linguagem RTL, entretanto, também inclui uma classe específica para montar uma string a partir de
fragmentos de diferentes tipos de dados, chamada TStringBuilder.

Como exemplo simples do uso da classe TStringBuilder , considere o seguinte trecho de código:

era
SBuilder: TStringBuilder;
Str1: sequência;
começar
SBuilder := TStringBuilder.Create;
tentar
SBuilder.Append(12);
SBuilder.Append(); 'Olá'
Str1 := SBuilder.ToString;
finalmente
SBuilder.Grátis;
fim;
fim;

Observe que temos que criar e destruir este objeto TStringBuilder . Outro elemento
que você pode notar acima é que existem muitos tipos de dados diferentes que você pode passar como
parâmetros para a função Append .

Outros métodos interessantes da classe TStringBuilder incluem um AppendFormat (com uma chamada
interna para Format) e um AppendLine que adiciona o valor sLineBreak . Junto com Append, há uma
série correspondente de métodos Insert sobrecarregados , bem como métodos Remove e alguns métodos
Replace .

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

514 - 18: outras classes principais de rtl

note A classe TStringBuilder possui uma interface agradável e oferece boa usabilidade. Em termos de desempenho,
porém, o uso de funções padrão de concatenação e formatação de strings pode fornecer melhores resultados,
ao contrário de outras linguagens de programação que definem strings imutáveis e têm desempenho muito ruim
no caso de concatenação pura de strings.

Encadeamento de métodos em StringBuilder


Uma característica muito específica da classe TStringBuilder é que a maioria dos métodos são funções que retornam o
objeto atual ao qual foram aplicados.

Este idioma de codificação abre a possibilidade de encadeamento de métodos, ou seja, chamar um método no objeto
retornado pelo anterior. Em vez de escrever:

SBuilder.Append(12);
SBuilder.AppendLine;
SBuilder.Append( 'Olá' );

você pode escrever:

SBuilder.Append(12).AppendLine.Append( 'Olá' );

que também pode ser formatado como:

Construtor.
Anexar (12).
AnexarLinha.
Acrescentar( 'Olá' );

Costumo gostar mais dessa sintaxe do que da original, mas sei que é apenas um açúcar sintático e algumas pessoas
preferem a versão original com o objeto escrito em cada linha. De qualquer forma, lembre-se de que as várias
chamadas para Append não retornam novos objetos (portanto, não há possíveis vazamentos de memória), mas
exatamente o mesmo objeto que você está invocando
os métodos ativados.

Usando listas de strings


Listas de strings são uma abstração muito comum usada por muitos componentes visuais, mas também são usadas
como uma forma de manipular uma coleção de strings. Existem duas classes principais para processar listas de strings:

· TStrings é uma classe abstrata para representar todas as formas de listas de strings, independentemente de suas
implementações de armazenamento. Esta classe define uma lista abstrata de strings. Por esse motivo, os objetos
TStrings são usados apenas como propriedades de componentes capazes de armazenar as próprias strings.

· TStringList, uma subclasse de TStrings, define uma lista de strings com armazenamento próprio.
Você pode usar esta classe para definir uma lista de strings em um programa.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 515

As duas classes de listas de strings também possuem métodos prontos para uso para armazenar ou
carregar seu conteúdo de ou para um arquivo de texto, SaveToFile e LoadFromFile (que são totalmente
habilitados para Unicode). Para percorrer uma lista, você pode usar uma instrução for simples baseada em
seu índice, como se a lista fosse um array ou um enumerador for-in .

A biblioteca Run-Time é bastante grande


Há muito mais no RTL que você pode usar em seu código Object Pascal, abrangendo muitos recursos
básicos para atingir vários sistemas operacionais. Cobrir detalhadamente toda a Run-Time Library
preencheria facilmente outro livro do mesmo tamanho que este.

Se considerarmos apenas a parte principal da biblioteca, que é o namespace System , ela inclui as
seguintes unidades (das quais removi algumas unidades raramente usadas):

· System.Actions inclui o suporte principal para a arquitetura de ações, que fornece uma maneira de
representar comandos do usuário conectados, mas abstraídos, da camada da interface do usuário.

· System.AnsiStrings possui as antigas funções para processamento de strings ANSI (somente no


Windows), abordadas no Capítulo 6.

· System.Character possui auxiliares de tipo intrínseco para caracteres Unicode (o Char


tipo), abordado no Capítulo 3.

· System.Classes fornece classes básicas do sistema e é a unidade que abordei detalhadamente em


a primeira parte deste capítulo.

· System.Contnrs inclui as classes de contêiner antigas e não genéricas, como lista de objetos, dicionário,
fila e pilha. Recomendo usar a versão genérica das mesmas classes, quando possível.

· System.ConvUtils possui uma biblioteca de utilitários de conversão para diferentes medidas


unidades.

· System.DateUtils possui funções para processar valores de data e hora.

· System.Devices faz interface com dispositivos do sistema (como GPS, acelerômetro e outros
sobre).

· System.Diagnostics define uma estrutura de registro para medição precisa do tempo decorrido em testes
de código, que usei ocasionalmente no livro.

· System.Generics possui na verdade duas unidades separadas, uma para coleções genéricas e outra
para tipos genéricos. Essas unidades são abordadas no Capítulo 14.

· System.Hash tem o suporte principal para definir valores de hash.

· System.ImageList inclui uma implementação abstrata e independente de biblioteca para


gerenciar listas de imagens e partes de uma única imagem como uma coleção de elementos.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

516 - 18: outras classes principais de rtl

· System.IniFiles define uma interface para processar arquivos de configuração INI, frequentemente
encontrados no Windows.

· System.IOUtils define registros para acesso ao sistema de arquivos (arquivos, pastas, caminhos), que
foram abordados anteriormente neste capítulo.

· System.JSON inclui as mesmas classes principais para processamento de dados na notação de objeto
JavaScript comumente usada, JSON.

· System.Math define funções para operações matemáticas, incluindo funções trigonométricas e financeiras.
Ele também possui outras unidades em seu namespace para vetores e matrizes.

· System.Messaging fornece tratamento de mensagens para diferentes sistemas operacionais.

· System.NetEncoding inclui processamento para algumas codificações de rede comuns, como


base64, HTML e URL.

· System.RegularExpressions define suporte a expressões regulares (regex) .

· System.Rtti possui todo o conjunto de classes RTTI, conforme explicado no Capítulo 16.

· System.StrUtils possui as funções principais e tradicionais de processamento de strings.

· System.SyncObjs define algumas classes para sincronizar aplicativos multi-threaded


ções.

· System.SysUtils possui a coleção básica de utilitários de sistema, com alguns dos mais tradicionais que
datam dos primórdios do Delphi.

· System.Threading inclui as interfaces, registros e classes do relativamente recente


Biblioteca de programação paralela.

· System.Types possui alguns tipos de dados adicionais básicos, como registros TPoint, TRectangle e
TSize , a classe TBitConverter e muitos outros tipos de dados básicos usados pelo RTL.

· System.TypInfo define a interface RTTI mais antiga, também apresentada no Capítulo 16,
basicamente substituído por aqueles da unidade System.RTTI .

· System.Variants e System.VarUtils possuem funções para trabalhar com variantes (um recurso de
linguagem abordado no Capítulo 5).

· System.Zip fornece uma interface para compactação e descompactação de arquivos comuns.


biblioteca de missão.

Existem também várias outras partes da RTL que são subseções do Sistema
espaço de nome, com cada seção abrangendo várias unidades (ocasionalmente um grande número, como
o namespace System.Win ), incluindo clientes HTTP (System.Net) e suporte à Internet das Coisas
(IoT) (System.Beacon, System.Bluetooth , System.Sensors e System.Tether). Existem também, é
claro, APIs traduzidas e arquivos de cabeçalhos para interface com todos os sistemas operacionais
suportados.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

18: outras classes principais de rtl - 517

Há uma grande variedade de funções, tipos, registros, interfaces e classes RTL prontas para usar,
que estão disponíveis para você explorar, para aproveitar o poder do Object Pascal. Não tenha
pressa navegando na documentação do sistema para saber mais.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

518 - para encerrar

para encerrar

O capítulo 18 marca o fim do livro, exceto pelos três apêndices seguintes. Este foi originalmente meu
primeiro livro focado exclusivamente na linguagem Object Pascal, e estou fazendo o meu melhor para
continuar atualizando-o e mantendo o texto do livro e o código-fonte ao longo do tempo. Fiz uma atualização
somente em PDF para o Delphi 10.1 Berlin, uma nova versão disponível impressa para 10.4 Sydney, e
esta atualização somente em PDF que você está lendo para o Delphi 11 Alexandria.

Consulte novamente a Introdução para obter o código-fonte do livro mais recente do GitHub (baseado
no mesmo repositório da edição 10.4) e visite o site do livro ou meu blog para obter informações e
atualizações futuras.

Espero que você tenha gostado de ler o livro tanto quanto eu gostei de escrevê-lo e de escrever sobre
Delphi nos últimos 25 anos. Boa codificação com Delphi!

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

fim. - 519

fim.

Esta seção final do livro tem alguns apêndices que enfocam questões secundárias específicas
que valem a pena considerar, mas que não se enquadram no fluxo do livro. Há também uma breve
história das linguagens Pascal e Object Pascal, bem como um glossário.

Resumo do Apêndice
Apêndice A: A Evolução do Object Pascal
Apêndice B: Glossário de Termos
Apêndice C: Índice

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

520 - a: a evolução do objeto pascal

a: a evolução de

objeto pascal

Object Pascal é uma linguagem desenvolvida para a crescente variedade de dispositivos de computação atuais.
de smartphones e tablets a desktops e servidores. Não apareceu simplesmente do nada. Ele foi
cuidadosamente projetado em uma base sólida para ser a ferramenta preferida dos
programadores modernos. Ele fornece um equilíbrio quase ideal entre a velocidade da
programação e a velocidade dos programas resultantes, clareza de sintaxe e poder de
expressão.
A base sobre a qual o Object Pascal foi construído é a família de linguagens de programação
Pascal. Da mesma forma que o Go do Google, ou o Objective-C da Apple, são linguagens
enraizadas em C, o Object Pascal está enraizado em Pascal. Sem dúvida você teria adivinhado isso
do nome.

Este pequeno apêndice inclui um breve histórico da família de linguagens e ferramentas reais em
torno de Pascal, Turbo Pascal, Delphi's Pascal e Object Pascal. Embora não seja realmente
necessário ler isto para aprender a língua, certamente vale a pena compreender a evolução da
língua e onde ela se encontra hoje.
A linguagem de programação Object Pascal que usamos hoje nas ferramentas de desenvolvimento
da Embarcadero foi inventada em 1995, quando a Borland introduziu o Delphi, que na época era
seu novo ambiente de desenvolvimento visual. A primeira linguagem Object Pascal foi estendida
da linguagem já em uso nos produtos Turbo Pascal, onde a linguagem foi

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

a: a evolução do objeto pascal - 521

geralmente referido como Turbo Pascal. A Borland não inventou o Pascal, apenas ajudou a fazer
é muito popular e amplia seus fundamentos para superar algumas de suas limitações em comparação com a
linguagem C.

As seções a seguir cobrem a história da linguagem, desde o Pascal de Wirth até o mais recente compilador
Object Pascal do Delphi baseado em LLVM para chips ARM e dispositivos móveis.

Pascal de Wirth
A linguagem Pascal foi originalmente desenvolvida em 1971 por Niklaus Wirth, professor da Politécnica de
Zurique, na Suíça. A biografia mais completa de Wirth está disponível em http://www.cs.inf.ethz.ch/~wirth.

Pascal foi projetado como uma versão simplificada da linguagem Algol para fins educacionais. O próprio Algol
foi criado em 1960. Quando Pascal foi inventado, existiam muitas linguagens de programação, mas apenas
algumas eram amplamente utilizadas: FORTRAN, Assembler, COBOL e BASIC. A ideia chave da nova
linguagem era a ordem, gerenciada por meio de um forte conceito de tipos de dados, declaração de variáveis e
controles estruturados de programas.
A linguagem também foi pensada para ser uma ferramenta de ensino, ou seja, para ensinar programação
utilizando as melhores práticas.

Escusado será dizer que os princípios fundamentais do Pascal de Wirth tiveram uma enorme influência na história
de todas as linguagens de programação, muito além daquelas ainda baseadas na sintaxe Pascal. Quanto ao
ensino de línguas, muitas vezes as escolas e universidades seguiram outros critérios (como pedidos de
emprego ou doações de fornecedores de ferramentas) em vez de procurar qual língua ajuda melhor a
aprender os conceitos-chave da programação. Mas isso é outra história.

Turbo Pascal
O mundialmente famoso compilador Pascal da Borland, chamado Turbo Pascal, foi lançado em 1983,
implementando o compilador de acordo com a especificação definida por "Pascal: User Manual and Report"
de Jensen e Wirth. O compilador Turbo Pascal foi uma das séries de compiladores mais vendidas de todos os
tempos e tornou a linguagem particularmente popular na plataforma PC, graças ao seu equilíbrio entre simplicidade,
potência e preço. O autor original foi Anders Hejlsberg, mais tarde pai das populares linguagens de programação
C# e TypeScript publicadas pela Microsoft.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

522 - a: a evolução do objeto pascal

Turbo Pascal introduziu o Ambiente de Desenvolvimento Integrado (IDE) onde você poderia editar
o código (em um editor compatível com WordStar), executar o compilador, ver os erros e voltar para
as linhas que contêm esses erros. Parece trivial agora, mas antes você tinha que sair do editor e
voltar ao DOS; execute o compilador de linha de comando, anote as linhas de erro, abra o editor e
localize as linhas de erro.
Além disso, a Borland vendeu o Turbo Pascal por 49 dólares, enquanto o compilador Pascal da
Microsoft foi vendido por algumas centenas. Os muitos anos de sucesso do Turbo Pascal
contribuíram para que a Microsoft abandonasse seu produto compilador Pascal.

Na verdade, você pode baixar uma cópia da versão original do Turbo Pascal da Borland na seção
Museu da Embarcadero Developer Network:

http://edn.embarcadero.com/museum

história Após a linguagem Pascal original, Niklaus Wirth projetou a linguagem Modula-2, uma extensão do
Sintaxe Pascal agora quase esquecida, que introduziu um conceito de modularização muito semelhante ao
conceito de unidades no antigo Turbo Pascal e no atual Object Pascal.
Uma extensão adicional do Modula-2 foi o Modula-3, que tinha recursos orientados a objetos semelhantes
ao Object Pascal. O Modula-3 foi ainda menos usado que o Modula-2, com a maior parte do desenvolvimento
comercial da linguagem Pascal migrando para os compiladores Borland e Apple, até que a Apple abandonou o
Object Pascal pelo Objective-C, deixando a Borland com quase o monopólio da linguagem.

Os primeiros dias do objeto Delphi


Pascal
Após 9 versões dos compiladores Turbo e Borland Pascal, que gradualmente estenderam a linguagem
para o domínio da programação orientada a objetos (OOP), a Borland lançou o Delphi
em 1995, transformando Pascal em uma linguagem de programação visual e introduzindo
possivelmente a melhor biblioteca para Windows já criada, a Visual Component Library ou VCL.
Delphi estendeu a linguagem Pascal de várias maneiras, incluindo muitas extensões orientadas a
objetos, que são diferentes de outras versões do Object Pascal, incluindo aquelas do compilador
Borland Pascal with Objects (a última encarnação do Turbo Pascal).

observação Você pode ler mais sobre o lançamento do produto Delphi em https://delphi.embarcadero.com/ e em https://
www.marcocantu.com/delphibirth/, e nas postagens do blog que geralmente escrevo todos os anos por volta
de 14 de fevereiro, para comemorar o aniversário do lançamento do produto.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

a: a evolução do objeto pascal - 523

história O ano de 1995 foi realmente um ano especial para linguagens de programação, pois viu a estreia do Delphi
Objeto Pascal, Java, JavaScript e PHP. Estas são algumas das linguagens de programação mais populares
ainda em uso hoje. Na verdade, a maioria das outras linguagens populares (C, C++, Objective-C e COBOL)
são muito mais antigas, enquanto a única linguagem popular mais recente é C#. Para uma história das
linguagens de programação você pode ver http://en.wikipedia.org/wiki/History_of_programming_languages.

Com o Delphi 2, a Borland trouxe o compilador Pascal para o mundo de 32 bits, na verdade reprojetando-
o para fornecer um gerador de código comum ao compilador C++. Isso trouxe muitas otimizações
anteriormente encontradas apenas em compiladores C/C++ para a linguagem Pascal.

No Delphi 3, a Borland adicionou à linguagem o conceito de interfaces, dando um salto na expressividade


das classes e seus relacionamentos.

Com o lançamento da versão 7 do Delphi, a Borland começou formalmente a chamar a linguagem Object
Pas-cal de linguagem Delphi, mas nada realmente mudou na linguagem naquela época. Naquela época,
a Borland também criou o Kylix, uma versão Delphi para Linux, e mais tarde criou um compilador Delphi
para o Microsoft .NET framework incluído no Delphi 8. Ambos
projetos foram posteriormente abandonados, mas o Delphi 8, lançado no final de 2003, marcou um
conjunto muito extenso de mudanças na linguagem, necessidade de suporte ao .NET, mudanças que foram
posteriormente adotadas no compilador Win32 Delphi e todos os outros compiladores seguintes.

Objeto Pascal do CodeGear para


Embarcadero
Com a Borland insegura sobre seus investimentos em ferramentas de desenvolvimento, versões
posteriores como o Delphi 2007 foram produzidas pela CodeGear, subsidiária da empresa principal. Esta
subsidiária (ou unidade de negócios) foi posteriormente vendida para a Embarcadero Technologies.
Após esse lançamento, a empresa voltou a se concentrar em crescer e estender a linguagem Object Pascal,
adicionando recursos há muito aguardados como suporte Unicode (no Delphi 2009), genéricos, métodos
ou fechamentos anônimos, informações ou reflexão de tipo de tempo de execução estendido e muitos outros
recursos. outros recursos linguísticos significativos (abordados principalmente na Parte III deste livro).

Ao mesmo tempo, junto com o compilador Win32, a empresa introduziu um compilador Win64 como
parte do Delphi XE2, e um compilador macOS, voltando a uma estratégia multiplataforma após a
tentativa fracassada de Borland e Kylix, sua versão do Delphi para Linux. Desta vez, porém, a ideia era ter
um único ambiente de desenvolvimento Windows e fazer compilação cruzada para outras plataformas.
O suporte para Mac foi apenas o começo da estratégia multidispositivo da empresa, abrangendo
plataformas desktop e móveis, como iOS e Android. Essa estratégia foi possível graças à adoção de um
novo framework GUI, denominado FireMonkey.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

524 - a: a evolução do objeto pascal

Tornando-se móvel
Onde todas as versões anteriores do Delphi tinham como alvo CPUs Intel x86, a mudança para dispositivos
móveis introduziu o primeiro compilador Object Pascal para chips ARM. Essa mudança levou a uma
rearquitetura geral do compilador e das ferramentas relacionadas, o “conjunto de ferramentas do compilador”, baseado em
a arquitetura aberta do compilador LLVM.

note LLVM é o nome abreviado de LLVM Compiler Infrastructure ou “uma coleção de compiladores modulares e
reutilizáveis e tecnologias de conjunto de ferramentas”, sobre as quais você pode ler em https://llvm.org/

O compilador ARM para iOS, lançado com Delphi XE4, foi o primeiro compilador Object Pascal baseado em
LLVM, mas também o primeiro a introduzir alguns novos recursos como Automatic Reference Counting
(ARC), posteriormente removido da linguagem.

Posteriormente, no mesmo ano (2013), o Delphi XE5 adicionou suporte à plataforma Android, com um
segundo compilador ARM baseado em LLVM. Para resumir, o Delphi XE5 vem com 6 compiladores para
a linguagem Object Pascal (para Win32, Win64, macOS, iOS Simulator no Mac, iOS ARM e Android ARM).
Todos esses compiladores fazem parte do Delphi atual e suportam uma definição de linguagem amplamente
comum, com algumas diferenças significativas que abordei detalhadamente ao longo do livro.

Nos primeiros meses de 2014, a Embarcadero lançou uma nova ferramenta de desenvolvimento baseada
nas mesmas tecnologias móveis básicas e chamada AppMethod. Em abril de 2014, a empresa também
lançou a versão XE6 do Delphi, enquanto setembro de 2014 viu o terceiro lançamento do AppMethod e
Delphi XE7, seguido, na primavera de 2015, pelo Delphi XE8, que incluiu o primeiro compilador ARM de 64
bits, visando iOS.

O período Delphi 10.x


Após o Delphi 10 Seattle, com a aquisição da empresa pela Idera Corp., a Embar-cadero criou a série de
lançamentos 10.x: Delphi 10.1 Berlin, Delphi 10.2 Tokyo, Delphi
10,3 Rio e Delphi 10,4 Sydney. Durante esse período, a Embarcadero adicionou suporte para novas
plataformas e sistemas operacionais alvo, ou seja, Linux de 64 bits, Android de 64 bits e macOS de 64 bits.
A empresa também voltou a se concentrar na biblioteca Windows VCL, adicionando suporte específico
para o sistema operacional Windows 10.

Ao longo da série 10.x, a Embarcadero continuou a evoluir a linguagem Object Pascal com a introdução de
recursos como declarações de variáveis inline e registros gerenciados personalizados, além de muitas outras
melhorias menores, todas abordadas neste livro.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

a: a evolução do objeto pascal - 525

A versão do Delphi 11
Com o lançamento do Windows 11 pela Microsoft e Apple se afastando de uma longa série de versões 10.x do
sistema operacional macOS, a Embarcadero decidiu sair da série 10.x para adotar uma numeração sequencial
de versões principais começando com 11 Alexandria – correspondendo assim Número da versão do Windows 11
neste momento. Isso foi feito para sublinhar a estreita ligação do produto com o sistema operacional Windows,
mesmo depois de ter adotado o desenvolvimento para vários dispositivos como um princípio central do Delphi
moderno. Notável no Delphi 11 é o suporte para a plataforma macOS ARM de 64 bits, com geração de código
nativo para a CPU Apple M1, e a estreia do IDE habilitado para High-DPI.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

526 - b: glossário

b: glossário

A
Classe abstrata Uma classe que não está totalmente definida e fornece apenas a interface
do método que as subclasses devem implementar.

Chamada ambígua Esta é a mensagem de erro que você recebe caso o compilador
tenha duas ou mais opções para resolver uma chamada de função
e não tenha como determinar automaticamente qual delas você
está tentando chamar.
Android O nome do sistema operacional do Google para dispositivos
móveis adotado por centenas de fornecedores de hardware
(além do Google) devido à sua natureza aberta. O Android é
atualmente o sistema operacional mais utilizado no mundo,
tendo ultrapassado o Microsoft Windows.

Método anônimo Um método anônimo, ou função anônima, é uma função que não
está associada a um nome de função e pode ser atribuída a uma
variável ou passada como argumento para
outra função, que posteriormente pode executar seu código.
Os métodos anônimos são um pouco mágicos em comparação com
as funções regulares. A verdadeira mágica é que eles podem
acessar variáveis do bloco em que são declaradas, mesmo que
finalmente sejam executadas em um bloco diferente.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

b: glossário - 527

A função anônima e as variáveis que ela pode acessar são


conhecidas como encerramento, que é outro nome para
métodos anônimos.
API Uma interface de programação de aplicativos (API) é fornecida
por software, incluindo sistemas operacionais, para que os
programas aplicativos possam se comunicar e trabalhar com ele. Por
exemplo, quando um aplicativo exibe uma linha de texto em uma tela,
normalmente ele chama uma função nos sistemas operacionais
que lida com a GUI. A coleção de funções fornecidas para fazer
interface com a GUI do sistema operacional é conhecida como API
da GUI.
Geralmente o software fornece uma API definida na linguagem
em que está escrito. Por exemplo, o Microsoft Windows, escrito em
C/C++, fornece uma API voltada para as linguagens C e C++.

Nota: A unidade Object Pascal WinAPI.Windows fornece uma API


Object Pascal para Microsoft Windows, eliminando o incômodo de
chamar diretamente funções escritas para C/C++.

B
Expressão booleana Nomeada em homenagem ao famoso matemático George Boole,
uma expressão booleana é uma expressão avaliada como
Verdadeiro ou Falso. Um exemplo simples é 1 = 2, que é falso. A
expressão booleana não precisa ser uma expressão matemática
tradicional, pode ser simplesmente uma variável booleana ou
mesmo uma chamada para
uma função que retorna um valor booleano.

C
Cardeal Um número cardinal é um dos números naturais. Simplesmente
dito, isso significa um número que pode ser usado para contar
coisas, como tal é sempre maior ou igual a zero.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

528 - b: glossário

Aula Uma classe é uma definição das propriedades, métodos e campos de dados
que um objeto (daquela classe) terá quando for criado.

Nota: Nem todas as linguagens orientadas a objetos requerem classes


para definir objetos. Objetos em JavaScript, IO e Rebol podem
ser definido diretamente sem primeiro definir uma classe.

Nota: A definição de um registro é muito semelhante à definição de uma


classe em Object Pascal. Um registro possui membros que cumprem a
mesma função que as propriedades de uma classe e procedimentos e
funções, que fazem o que os métodos fazem para uma classe.

Ponto de código O valor numérico de um elemento do conjunto de caracteres Unicode.


Cada letra, número e pontuação de cada alfabeto do mundo
possui um ponto de código Unicode que o representa.

Diretiva do compilador Uma diretiva de compilador é uma instrução especial para o compilador
que altera seu comportamento padrão. As diretivas do compilador
são atribuídas com palavras especiais prefixadas pelo sinal $ ou podem
ser definidas nas Opções do Projeto.
Nota: Algumas diretivas de compilador fornecem, por razões históricas,
versões de um único caractere de seus nomes mais longos.

Componentes Os componentes são objetos de código pré-construídos e prontos para uso


que podem ser facilmente combinados com o código do aplicativo e
outros componentes para reduzir drasticamente o tempo necessário para
desenvolver aplicativos.
As bibliotecas VCL e FireMonkey são duas grandes coleções desses
componentes fornecidas com o Delphi.

COM Component Object Model (COM) é uma parte central da arquitetura do


Microsoft Windows.

Ao controle Um controle é um elemento de uma GUI, como um botão, um campo de


entrada de texto, um contêiner de imagem, etc. Os controles são
frequentemente indicados como componentes visuais.

CPU A Unidade Central de Processamento (CPU) é o núcleo de qualquer


computador e é o que realmente executa o código que você escreve.
As instruções da linguagem Object Pascal precisam ser traduzidas em
código assembly para serem compreendidas pela CPU.
Observe que você tem um visualizador de CPU no depurador Delphi.
A CPU geralmente funciona junto com uma FPU integrada na mesma matriz.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

b: glossário - 529

D
Tipo de dados Um tipo de dados indica o requisito de armazenamento e as
operações que você pode executar em uma variável desse tipo. Em
Object Pascal, como em qualquer linguagem de programação
fortemente tipada, cada variável possui um tipo de dados específico.

Padrões de design Observando arquiteturas de software que diferentes desenvolvedores


usam para resolver problemas diferentes, você pode notar
semelhanças e elementos comuns. Um padrão de projeto é o
reconhecimento de um projeto comum, expresso de maneira
padronizada e abstraído o suficiente para ser aplicável em diversas
situações diferentes.
O movimento dos padrões de design no mundo do software
começou em 1994, quando Erich Gamma, Richard Helm, Ralph
Johnson e John Vlissides escreveram o livro “Design Patterns,
Elements of Reusable Object-Oriented Software” (Addison-Wesley, 1994,
ISBN: 0- 201-633612).
Os autores são frequentemente chamados de “Gangue dos Quatro”
ou simplesmente “GoF”. O livro é frequentemente referido
coloquialmente como o “livro GoF”.
No livro GoF os autores descrevem a noção de padrões de software,
indicam uma forma precisa de descrevê-los e fornecem um catálogo de
23 padrões, divididos em três grupos: criacionais, estruturais e
comportamentais.
DLL Uma Dynamic Link Library (DLL) é uma biblioteca de funções que não
estão incluídas no código executável de um aplicativo, mas armazenadas
em um arquivo separado. Então, quando o aplicativo é executado, ele
carrega a biblioteca na memória e pode chamar as funções contidas na
biblioteca. Essas bibliotecas
normalmente são projetados para serem usados por muitas aplicações.
Em plataformas diferentes do Windows, esse tipo de biblioteca é
chamado de Objeto Compartilhado (ou SO).

Evento Um evento é uma ação ou operação que acontece em um


aplicativo, como um clique do mouse ou um redimensionamento de
formulário. Delphi implementa eventos através de uma propriedade especial

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

530 - b: glossário

de uma classe que permite a um objeto delegar algum “comportamento”


a um método externo. Os eventos fazem parte do modelo de
desenvolvimento RAD.

F
FireMonkey FireMonkey (FMX) é uma biblioteca de componentes visuais e não
visuais fornecida com Delphi (além da biblioteca VCL). Os
componentes são multiplataforma, portanto funcionarão igualmente
bem em Windows, macOS, iOS,
Android e até Linux (por meio da biblioteca complementar
FMXLinux).
Forma O termo usado para uma janela nas bibliotecas VCL e FireMonkey.

Sistema de arquivo Um sistema de arquivos faz parte do sistema operacional de um


computador que organiza como os dados são armazenados no computador
e gerencia o armazenamento e a recuperação de dados.

PUF A Unidade de Ponto Flutuante (FPU) é uma companheira da CPU


focada na execução extremamente rápida de cálculos complexos
de números de ponto flutuante.
Função Uma função é um bloco de código que executa alguma ação (ou
cálculo) e retorna um resultado. Ele pode aceitar um número pré-
especificado de parâmetros para operar. Veja também procedimentos
e métodos.

G
Memória global A memória global é uma área de memória estática para variáveis
globais de suas aplicações. Essa memória é usada durante todo o
tempo de vida de uma aplicação e não pode crescer, diferentemente
do heap que fornece uma área de memória alocada dinâmica. A
memória global é usada com moderação em aplicativos Object
Pascal.
GUI Uma interface gráfica do usuário (GUI) permite que os usuários interajam
com computadores, tablets e telefones através de gráficos

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

b: glossário - 531

ícones e outros indicadores visuais. A maior parte da interação do


usuário com uma GUI é realizada apontando, tocando, pressionando,
deslizando e outros gestos usando um mouse (ou dispositivo
apontador semelhante) ou dedos.

Pilha (memória) O heap é uma área de memória para blocos de memória alocados
dinamicamente. Como o nome indica, não há estrutura ou
sequência na alocação de memória heap. Sempre que um
é necessário um bloco, ele é retirado de uma área livre. A vida útil
dos blocos individuais é diferente e a ordem de alocação e
desalocação não está relacionada. A memória heap é usada para
dados de objetos, strings, arrays dinâmicos e outros tipos de
referência (veja Referências), mas também para blocos alocados
manualmente (veja Ponteiros). O heap é grande, mas não infinito
e, se você não liberar objetos não utilizados da memória, seu
aplicativo acabará ficando sem recursos.
memória.

EU

VAI Um Ambiente de Desenvolvimento Integrado (IDE) é um aplicativo


único que fornece ao desenvolvedor uma ampla gama de
ferramentas para que ele possa ser altamente produtivo. No mínimo,
um IDE fornecerá um editor de código-fonte, ferramentas de
automação de construção e um depurador. A ideia moderna de
um IDE foi inventada junto com os primeiros compiladores
Turbo Pascal que vieram da Borland, os precursores dos atuais IDEs
Object Pascal da Embarcadero Technologies.

O IDE Object Pascal fornecido com Delphi é muito sofisticado


e inclui, por exemplo, um designer de GUI, modelos de código,
refatoração de código e testes de unidade integrados.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

532 - b: glossário

Herança de tipo A herança de tipo é um dos princípios básicos da programação


orientada a objetos (OOP). A ideia é que um tipo de dados possa
estender um tipo de dados existente, adicionando novos recursos a
ele. Essa extensão de tipo é conhecida como herança de tipo,
junto com termos como classe base e descendente, ou classe pai e
classe filha.

Interface Geralmente se refere a uma declaração abstrata do que um módulo


de software pode fazer. Em Object Pascal, uma interface é uma
definição de classe puramente abstrata (feita apenas de métodos e
sem dados), como em C# ou Java. (Veja o Capítulo 11 para cobertura
completa.)
A linguagem também possui o conceito de interface para uma unidade,
nesse caso, esta é a seção da unidade que declara o que é visível
para outras unidades. A mesma palavra-chave de interface é usada
em ambos os casos.

iOS O nome do sistema operacional que alimenta os dispositivos


móveis da Apple.

M
Mac OS O nome do sistema operacional dos computadores Apple Mac,
anteriormente conhecido como OS X.
Método Um método é uma função ou procedimento vinculado a um objeto.
Os métodos têm acesso a todos os dados armazenados no objeto.

Objeto Um objeto é uma combinação de alguns itens de dados


(propriedades e campos) e código (métodos). Um objeto é uma
instância de uma classe que define uma família (ou tipo) de
objetos.
ABRIR A programação orientada a objetos (OOP) é o modelo por trás
do Object Pascal, baseado em conceitos como classes, herança
e polimorfismo. Objeto Moderno Pascal

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

b: glossário - 533

também suporta outros paradigmas de programação, graças a


recursos como genéricos, métodos anônimos e reflexão.

Tipo ordinal Um tipo ordinal é um tipo de dados feito de elementos que podem ser
contados e têm uma sequência. Você pode pensar em números inteiros,
mas os caracteres também têm uma sequência, assim como os tipos
enumerados personalizados.

Sobrecarga A sobrecarga de funções é um recurso de linguagens de programação


que são rigorosas quanto aos tipos de variáveis, permitindo que um
programador declare diferentes versões de uma função que pode aceitar
diferentes tipos de parâmetros.

P
Ponteiro Um ponteiro é uma variável que contém um endereço de memória. Um
ponteiro pode se referir à localização de alguns dados ou de uma função
na memória. Ponteiros não são comumente usados, enquanto referências
(veja Referência) são ponteiros opacos e gerenciados que são
extremamente comuns, mas também significativamente mais fáceis
de usar.

Polimorfismo Polimorfismo é a capacidade de uma chamada a um método assumir


“formas diferentes”, ou seja, acabar realizando operações diferentes,
dependendo do objeto ao qual se aplica. É uma característica padrão das
linguagens OOP.

Procedimento Um procedimento é um bloco de código que pode ser chamado de outras


partes de um programa. Um procedimento pode aceitar parâmetros para
variar o que faz. Diferentemente de uma função, um procedimento não
retorna um valor. Ao contrário de Pascal, a maioria das outras linguagens
não faz distinção e nomeia procedimentos e funções, funções.

Opções de projeto Um conjunto de opções de configuração que afetam a estrutura geral


de um projeto de aplicativo, mas também como o compilador e o vinculador
se comportam.

Propriedade Uma propriedade define o estado de um objeto. Dado que uma propriedade
pode ser mapeada para dados ou usar métodos para ler e escrever o valor,
ela pode ser usada para abstrair da implementação real.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

534 - b: glossário

R
RAD O Desenvolvimento Rápido de Aplicativos (RAD) é uma característica
de um ambiente de desenvolvimento que torna fácil e rápido
para construir aplicativos. As ferramentas RAD são geralmente baseadas
em designers visuais, embora esta seja uma definição bastante antiga e
raramente usada hoje em dia.

Registro Um registro simples é uma coleção de itens de dados armazenados


de forma estruturada. Os registros são definidos em uma definição de tipo
que mostra a ordem e o tipo dos itens de dados individuais no registro.

Object Pascal também inclui registros avançados que podem ter métodos
semelhantes a um objeto.

Recursão Recursão ou chamada recursiva é uma forma de descrever uma função


que continua chamando a si mesmo até que uma determinada condição seja
atendida. Uma chamada recursiva costuma ser uma alternativa mais elegante a um loop
ou ciclo, mas usar recursão tornou-se menos comum atualmente. Um
exemplo de implementação recursiva de uma função de multiplicação seria
pegar o valor do primeiro número e adicionar a ele o resultado da chamada
da mesma função passando o segundo número diminuído de um e
continuando a chamar a função até este segundo número se torna zero.

Referência Uma referência é uma variável que se refere a alguns dados em outro
local da memória, em vez de armazená-los diretamente. Em Object
Pascal, estas são variáveis para tipos como
classes e strings, mas também interfaces e arrays dinâmicos são
referências. Diferentemente dos ponteiros (veja Ponteiros), as
referências são geralmente gerenciadas pelo compilador e por uma
biblioteca de tempo de execução e requerem pouco conhecimento de
baixo nível e pouco gerenciamento direto de memória, se houver, por
parte do desenvolvedor.

RTTI (ou reflexão) Um acrônimo de Run Time Type Information, é a capacidade


para acessar informações de tipo (tradicionalmente disponíveis apenas para
compiladores) no aplicativo real em tempo de execução. Outros ambientes
de programação referem-se a esse recurso como reflexão.

Biblioteca de tempo de execução (RTL) Esta é uma coleção de rotinas pré-escritas que o compilador inclui
automaticamente com o código do aplicativo para suportar o aplicativo
executável. Inclui suporte

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

b: glossário - 535

para muitas operações fundamentais, especialmente aquelas


que requerem interação com o sistema operacional quando o
a aplicação é executada (por exemplo, alocação de memória, leitura e
gravação de dados, interação com o sistema de arquivos).

SDK Um Kit de Desenvolvimento de Software (SDK) é um conjunto de


ferramentas de software com as quais é possível construir software
para um ambiente específico. Cada sistema operacional fornece um
SDK incluindo as bibliotecas de interfaces de programação de
aplicativos (API) e ferramentas de desenvolvedor necessárias para
construir, testar e depurar aplicativos para a plataforma.

Caminho de pesquisa Um conjunto de pastas que o compilador irá pesquisar ao procurar


uma unidade externa referenciada em uma instrução de uso

Pilha (memória) A pilha é uma área de memória alocada de forma dinâmica e ordenada.
Cada vez que você chama um método, procedimento ou função,
ele reserva sua própria área de memória (para variáveis locais,
inclusive temporárias, e parâmetros).
À medida que o método retorna, a memória é limpa, de forma muito
ordenada. O único cenário real para ficar sem
memória de pilha é quando um método entra em uma chamada
recursiva infinita (consulte Recursão).

Nota: Na maioria dos casos, as variáveis locais alocadas na pilha


não são inicializadas com zero: você precisa definir seu valor antes
de usá-las.

EM

Unicode Unicode é uma forma padrão de registrar caracteres de texto


individuais como dados binários (uma sequência de 0s e 1s). O texto
pode ser trocado entre programas de forma confiável, processado e
exibido se estiver em conformidade com o padrão Unicode.
O padrão é muito grande, cobrindo mais de 110.000 caracteres
diferentes de cerca de 100 alfabetos diferentes.
e roteiros.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

536 - b: glossário

EM

VCL A Visual Component Library (VCL) é um grande conjunto de


componentes visuais fornecidos com Delphi. Os componentes GUI
da VCL são componentes GUI nativos do Windows.
Veja também FireMonkey para uma biblioteca alternativa de
plataforma cruzada.
Métodos virtuais Um método virtual é uma função ou procedimento declarado na
definição de tipo de uma classe que pode ser substituído por seu
subclasses. A chamada classe base também pode incluir definições
que não possuem implementações para métodos que podem ser
definidos pelas subclasses. Tais definições são conhecidas
como métodos abstratos.

EM

Janela Uma janela é uma área da tela que contém elementos GUI com os
quais um usuário pode interagir. Um aplicativo GUI pode exibir várias
janelas. No VCL e no FireMonkey, as janelas são definidas usando
um objeto Form.
janelas O nome do sistema operacional onipresente da Microsoft, que
foi pioneiro (junto com outros sistemas operacionais da época, como
System X para Apple Macintosh) no conceito
de janelas gráficas (ver Janela).

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 537

c: índice

1 Andreas Toth................................................. .......5


Android.................................47, 524, 526, 530 Colchetes
Página de código 1252................................................. .167 Angulares........................................385, 388 Delegado
1900................................................. ...................80 Anônimo ....................................431 Manipuladores de eventos
1983.......................... ....................................521 anônimos........ ..................439 Métodos Anônimos..........................
1995.......... ....................................................3 , 522 .431, 526 ANSI......................................... ...................196
AnsiChar.......................... ....................... 65, 192
6 AnsiString............ .................................198
API............... ...................................................527, 535
64 bits.................................61, 68, 162, 394, 524
Anexo.. .................................................. ........513
Maçã........................................ .......522, 532, 536 Ferramenta
A de Instrumentos da Apple........................374
Abstrato................................................. ..35, 245f. Aplicação.. .................................................. ..267
Classes abstratas...................................311, 526 Violações AppMethod............................................. .......524
de acesso.... ........................................378 Lista de ArcCosh......................................... ....................69 Chips
Ações........ ................................................305 Adaptador ARM........................... ...........................524
Padrão................................................327 Aditivo Matriz..................... ....................................34, 126 Propriedades
Operadores...................................................78 da matriz........ ............................284, 307 Matrizes de
AddRef... .................................................. .....314f. Registros............... ............................139
Endereço de........................................141, 146, 161 Pós- Como.................... .........................249, 314, 326, 378
Destruição. ...........................................268 ASCII................. ............................................. 166 Orientado a
aspectos Programação........................473
AJAX.... .................................................. .........445
Algol......................................... ....................52, 521 Montador........................ ................................521
Alinhamento.......................... ......................140, 225 Allen Atribuir.................. ..............................227, 359, 504
Bauer............ ...........................6, 123 Atribuído.............. ....................................163, 379
Alfabeto................... .......................................166 Chamadas Atribuições.......... ......................................50, 52 Classes de
Ambíguas........ ..............................113, 526 Anders Atributos....... ....................................466
Hejlsberg............... ..............202, 242, 521

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

538 - c: índice

Atributos................................................. .......465 Tamanho Personagens................................................. ........65


Automático......................................... .................225 Caracteres[]...................................... .............182f., 187 Caixa
de seleção......................... ...........................87 Verificando a
B memória.................. .......................368
Cr......................... ...................................67, 84 Chris
Arredondamento do banqueiro”.........................................84 Barry Bensen....... ...........................................285
Kelly.. ...................................................462, 470 Classe.... .................................................. ....35, 528 Conclusão
BÁSICO.. ................................................29, 49, 521 Plano Multilíngue
da aula......................................... ...204 Restrições de
Básico.........................169 Antes da Construção......... ..............................268 classe......................................... 398 Construtores de
Começar.................. ................................................34 Bertrand
Classe..........................40, 336, 396 Dados de
Meyer................................................230 Decimal Classe............ ...........................................330 ajudantes de
Binário .................................................. 51 Operadores bit a
classe.... ................................................344 Métodos de
bit.............................................78 BOM Classe. ................................................330 Classe
Marcador................................................. ...511 De................................................. .......... Operador de Classe
Booleano............................................. ..........60, 64
339.................................... ............148 Propriedades da
Booleano.......................... ........................527 Expressão
Classe.................................... ...........334 Referências de
Booleana........................ ..................527
classe.................................. .........339 Classe
BoolToStr.............................. ............................65 Var......................................... ....................331
Borland.................. ...................................521f. Aulas.......................... ................................202 Informações da
classe................. ....................................490 Nome da
Quebrar................................................. ......97f., 108 estouros Classe....... ....................................340, 489f.
de buffer...................................... .........371 ClassNameIs.................................................. ..490
Botão1Clique......................................... ...............22
ClasseParente............................................. .......490 Tipo de
bytes................................. ................................... Marca de classe......................................... .............489f.
ordem de 60 bytes............ ................................170
ByteLength................ ........................................193

C
C...28, 31, 61, 63, 65, 78, 81, 86f., 89, 91, 94, 100, 102, 108,
120, 126, 144 C/C+
+......... .................................................. ....24 C#. 25, 28, 49, 52,
70, 78, 90, 101, 142, 202, 204, 207, 217, 228, 234, 239, 242,
247, 249, 256, 276f., 284, 287, 291ss., 310, 329, 383,
400, 431 C++...28, 42, 49, 63, 101, 109, 126, 147, 202, 211,
217, 223, 239, 256, 310, 383, 426, 523 Funções de retorno de
chamada ....................................333 Convenção
de Chamada...... ...................................356 Convenções de
Chamada............ ............................119 Carcaça de
camelo......... ....................................29 Variável
capturada........... ................................434
Cardeal................. ....................................60, 527
Careta......... .................................................. ......161 Cary
Jensen......................................... ................6
Claro................................................. ...............376ClienteDataSet......................... ...
Caso................................. ...........................34, 88f.
Exemplo................................................. ....264
RTTI.......................................... ...............452
Insensibilidade a maiúsculas e minúsculas.................................28
$ALINHAR................................. ......................141
Pegar................................................. ..............256
$DEFINIR.......................... ....................45, 371
Cdecl.................................. ..............................119
$OUTRO......................... ...................................45
Caráter.................. ...................................60, 65s., 173
$HIGHCHARUNICODE.......... .............176f.

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 539

$SE................................................ ..........45, Dia da semana................................................. ......81 Lei de De


47$IFDEF................................... ..........45ss., 122 Morgan......................................... ......95
$IFEND................................... ................45, 47 Depurador......................................... ...............257
$IFNDEF.......................... ........................45 Dez................................. ............................62, 174
$INCLUIR........................ ....................42, Datadecodificação.................. ....................................81
47$INLINE........................ ............................116 Padrão........... ....................................284, 394 Restrição do
$J.................. ................................................57 milhões de construtor padrão... ...................404 Parâmetros
dólares ...........................................288f., 449, 504 $ padrão.......................... ..............114
RTTI.................................................. .....452f. DefaultTextLineBreakStyle.........................498
$SCOPEDENUMS........................................74 DefaultUnicodeCodePage.... ...........................193 Carregamento
$StrongLinkTypes...... ................................454 Atrasado.................. ...........................123
$VARPROPSETTER................ ..................285 Delegação...................... ....................291, 293
Excluir..................... ...........................................133
Delfos...... ......................4, 118, 153, 522, 528, 531 Delphi
$WeakLinkRTTI.......................... ...........453$Z........................................
10.4...32, ............................73
65, 106, 140, 147, $ZEROBASEDSTRING..................
149, 183, 198, 355, 363, 420, 423, .........183
Diretivas do Compilador..................26, 44, 528 Versões do 524 Delphi 11.........51, 68, 82,
Compilador............. ..............................45 Contagem de 315, 412, 509, 518, 525
Componentes................. ...........................505 DeQuoted.......... .............................................186 Padrões de
Componentes...................... ......................505, 528 Declarações Projeto. ............................................. 529 Tempo de
Compostas........................ ..............86 Concatenando design .................................................. ...287
Strings......................... ....184 Definições Destruir.........................................220 , 233, 361
Condicionais.........................................45 Aplicação de Destruidor...................................35, 220, 361, 375
console.................................20 Dicionário... .................................................. ...412 Separador
Const.... .................................................. ...34, 359 Referência de dígitos......................................... ......51
Const.......................................... .....110 Parâmetros Descarte......................................... ..................162
Constantes.........................110 DisposeOf.............................. ........................... 363
Constantes. .................................................. .......56 DLL...................... ...........................................529
Construtores............35, 219, 243, 267, 305, 375 Faça..... .................................................. .......35, 256
Contém........ ...........................................186 DoCompare......................................... ..............410 Nomes de
Continuar..... .................................................. ...97f. Unidades Pontilhadas........................ ...........39
Caracteres de controle.................................65 Duplo.................................... ....................34, 68
Controles.. .................................................. .......528 Até.......................... ..............................35, 91
Conversões......................................... ........53, 83 DupeString................ ....................................189
ConverterFromUtf32...................................... ....175 Dinâmico.......... ........................................244, 297 Matrizes
Cópia.......................................... ...........131f., 186 Cópia na Dinâmicas..... ...................................129, 132 Vinculação
gravação.............................. ....................179 Dinâmica........... ...................................239 Biblioteca de link
CountChars.......................... .........................186 Tipos de dinâmico............. ........................529
retorno covariante............. .............426
CPU.................................... ....68, 84, 96, 528, 530 E
Criar.................................. ..233, 375, 488, 506 Criando um
componente...................................298 Vinculação antecipada.................................................. ..239
Cores do Editor............................................. ........32
Moeda. .................................................. ........68
Corrente........................................ ....................301 EDivByZero........................................ .............258
EExternalException................................. .....123
Eflexão........................................... ...............534
D
EInvalidCast......................... ..................249
Tipos de dados................................................ 59, 529 Elixir.............................. ...................................105
Data......................................... ............80 Mais.............. ................................................34, 87
DateTimeToStr........................... .......................81 David Embarcadero...........................431, 520, 522, 524 Tecnologias
I........................ ....................................6 Embarcadero............ ...4f., 523, 531

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

540 - c: índice

Vazio................................................. .............178 DataEvento..................................296, 300 Pacote de


Encapsulamento..........42, 144, 211, 234, 276, 289 Data... ...........................................300 Propriedades de
EncodeDate........... ...........................................81 Data..... ...................................282
Fim.... .................................................. ............34 Datas1.......... .............................................206
FimDoStream.......................... .................512 Termina Datas2.. .................................................. ....212
com......................... .........................186 Tipos Datas3......................................... ............222
Enumerados...................... ....................73 Datas4.................................... ....................224 Datas
Enumeração.......................... .........................301 Derivadas.......................... ...............231f.
EProgrammerNotFound........................ ..........259 Eventos Dinâmicos..................................294
Iguais......................................... ................186, 493 Erich DynArray..... ...................................................130f.
Gama.......................... .....................529 Erik Van DynArrayConcat........................................133 Teste de
Bilsen.......................... ..............265, 426 Codificações....... ....................................196 Log de
Erlang.................................. ..............................105 Insight Erros............ ....................................267
de erro................. ....................................32 ExcetoFinalmente....... ....................................262f.
EurekaLog............ ...........................................262 Fluxo de Exceção..................................261 Teste de
Programação Orientada a Eventos.. .........................290 Exceções... .................................256 Teste de
Eventos........................ ...........290ss., 295, 304, 529 Expressões......... .................................77 Teste de
Exemplo.............................. .................................. flutuação............... ..............................69 Teste de
AvançadoExceto...................270f., 273 Fluxo......... ....................................97, 107
AlignTest.............. .......................................141 FormatString........ ....................................191
Animais1......... ........................................237ss. Propriedades do Formulário.......... ................................280
Animais2.................................................. .239f. ForTest................. ....................................92f.
Animais3................................................. ...246 Teste de Funções..................................102 Teste de
AnonAjax.........................................444, 446 Funções... ...................................101, 104f.
BotãoAnon...............................................439 GenericClassCtor........................................397
AnonLargeStrings...................................441 GenericCodeGen........ ................................392 Interface
AnonymFirst......... ....................431, 434, 436 Genérica.................. ......................417f.
ArcExperimentos......................... ..................365 GenericMethod..................................390
ArraysTest.............................. ............126, 128 GenericTypeFunc..... ................................394f.
AutoRTTI.................................. .................289 Arquivos HelloConsole................................................20
binários......................... ..................512 Teste de HelloPlatform ...............................................47
Caso.............................. .........................89 Teste de OláVisual.. ................................................22 Teste de
Caracteres........................ ......................66f., 92 Teste Identificadores .............................................28
de gráfico......................... ..............................175 SeTeste... .................................................. .....87f.
Restrição de Classe.................. ....................... 398 Teste de Inlining................................................ ..117
ClassCtor.......................... ...........................337 IntegersTest.................................61, 63
Demonstração do ClassHelper............. .................345 InterceptBaseClass.. ..... ..............................471
RefClasse......................... ...............341, 343 Intf101............. ..... ..............................312, 314
ClasseEstática......................... ...........332, 335 Contagem IntfConstraint.......... ...... ..........401, 403f., 418
de cliques................................... .............214 pontos de IntfContraints......................... ..... ............417
código................................... ..............167 Demonstração Intf................................ .... ........320, 325
ControlHelper.......................... ..........346 Erro Intf................................ ...... ...........316f.
ControlsEnum.................................... .....350 IoFilesInFolder.........................................507
ContagemObj......................................... .........335 KeyValueClassic..... ...................................384
CriarComps.................................217 , 219 Dicionário do KeyValueGeneric............. ...........................386
Cliente.................................412 StringGrande...................... ...................185, 441 Teste
Data3.......... .............................................. 220f. de Vazamento........................... ...........................370
DataComp................................................. .299 ListaDemoMd2005...................... .............407f.
DateComponent........................................300 Teste de Loops.................................................. .. ...94
DateCompTest..... ....................................300 Classe Aninhada......................................... ..228

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 541

NestedTypes................................................227 Funções Externas........................................122


NumbersEnumerator ................................301
ObjFromIntf................ ..............................326 F
OpenArray.................. ........................ 134, 136
Fabrício Schiavi.................................................. ...5
Operadores Acima ...................... ...................150f.
Teste de sobrecarga...................................111, 113f. Falso............................................. ............64 Chamada
Teste de Parâmetros................................................108ss. rápida......................... ...................................119
Teste de ponteiros........................................161, 163 RápidoMM4............ ...................................369, 371
ProcType..... ................................................121 Alinhamentos de Campos.......... ..................................140
Proteção ................................................235f. Arquivo.................. .................................................. ...34
ReaderWriter.................................511 Métodos de Acesso a arquivos........................................... ...........507
registro. ...................................144, 147 Sistema de Arquivos................................... ..................530
RegistrosDemonstração.............. ................................137 Tipos de arquivo.......................... ...........................164
Arquivos..................... ..................................................
Teste de Registros................. ........................139, 141
ReintroduzirTeste...................... ..................243 DCU.................................................. ............42
RttiAccess......................... ..................463f. DFM.................................. ...............287, 308
RttiAttrib................................................. 467f. DPR......................... ....................23, 43
RttiIntro.................................................. ....451 FMX..................... .............................. 287, 308
SafeCode..................................375, 377, 380 INC................. ................................................42
INI. .................................................. ...........516
ShowMemory.. ...........................................369
MostrarUnicode..... ....................................171 Ponteiros PAS.................................... ....................23, 42 Métodos
Inteligentes....... ....................................423 Finais.......................... ........................247
SmartPointersMR........... ...........................423 Chamada Finalização........................ .................34, 39, 337
de Retorno Estático..................... .......................333 Finalmente........................... 36, 256, 262f., 360, 375f.
StringHelperTest.......................... ...............187 FindComponent................................................505
FindHInstance. ................................................379
StringListVsDictionary..............................416
StringMetaTest. ........................................192f. FindType. .................................................. ......454
FireDAC......................................... ..................48
Cordas101.................................................. ..179
TempoAgora................................................ ........81 FireMonkey....21, 23, 39, 74, 305, 449, 503, 506, 523, 528,
530 Ponto
TipoAliasHelper........................................ 352
TypeCompRules................................387, 389 Flutuante... ....................................................67
FloatToDecimal.................................................84
TypeList............ ................................454, 458 Teste de
Variáveis.............. ...........................53, 56 Teste de FloatToStr................................................. ........84
Variante................... ...........................158f. FMXLinux........................................ ...............530
Para.................................. ...........................35, 90f.
VarProp.................................................. .....285
ViewDate.................................225, 231, 282 Para dentro.................................................. .........Formulário
VisualInheritTest. ...................................251 93, 301.................................... ........304f., 530, 536
WebFind.......... ................................443f., 446 Formato................................... ....84, 135, 186, 190
XmlPersist............. ....................................479 FormatoDataHora...................................... .81, 160
FormatFloat............................................. .........84
Exceto............ .........................36, 256f., 259, 263
Formulários........................................ ....................... 280
Exceção................. ........................270, 272, 338 Tratamento
FORTRAN.......................... ..............................521
de Exceções................... .......36, 255, 375 Hierarquia de
Avançar.................. ....................................103 Declarações
Exceções.................................... .258
Excluir................................................ ...............76 futuras...... ................................103
Sair................................. ....................98, 107f. FPU.................. ...........................................68.530 FPU

Explícito................................................. ...........149 Contexto integrados no mesmo Morrer...................528


Grátis.......................... ......208, 221, 233, 268, 361
de Expressão................................... .....431
LivreENil.................................. .........209, 362f.
Expressões........................................... ..........76f.
Estendido................................................. ..........68 RTTI Instância Gratuita................................................... ..374
Estendido.......................... ...........451 FreeMem............................................. ...162, 372

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

542 - c: índice

Aulas de amigos.................................................. ..211 IDE......20, 24, 112, 145, 250, 279, 288, 299, 522, 531
De................................................ ..................462
Função.............................. ..............35, 101, 530 Ponteiro de Identificadores......................... ...................................27f.
Função........................... ................120 IDispatch................................................. ........499 Comparador
de IEqualidade........................................ .419
G Se ................................................ ...................34, 87f.
Se então................................................. .....172, 189
Bando dos Quatro............................................... ...529 Coleta IInterface..............................311ss., 321, 325, 499
de lixo.........................208, 354 Restrições IInvokable.. .................................................. ...499
genéricas...... ...................................398 Contêineres
Implementação.........................................34, 38
Genéricos............. ............................406 Dicionário
Implementos................................................322, 499
Genérico............ .......................412 Métodos Implícito ....................................149, 152, 425, 462
Genéricos........................ ....................390 Declaração de Tipo Em...... .................................................. .........35, 75
Genérico.......................... .....389 Inc......................................... .................63, 174, 279
ObterDiretórios........................................... ......507 Incluir........................... ............................42, 76
GetEnumerator.........................................94, 301f . Recuo................. ....................................30
Obter arquivos.................................................. ..........508 Indexadores.......... ................................................284
ObterHashCode.........................186, 475, 493 ÍndiceDe .................................................. ..........186
ObterMem.........................................162, 357, 372
IndexOfAny.................................... ...............186 Ocultação de
GetMemoryManagerState..............................368 informações................................. .........211
GetMemoryMap................. ............................368 Herança....................................230 , 237, 243
ObterAlinhamento de Bloco Mínimo................... .....370 Herdado...................................35, 222, 243, 253 Herdado
GetNumericValue......................................... .174 Obter de.. ..............................340, 378, 490f.
Pacotes............................................... .....460 Inicialização........................................34, 39, 337 Em
GetPropValue......................................... ......290 linha... .................................................. .....35, 116 Variáveis
GetType.......................................... .................454 Inline........................................ ...54, 90
GetTypeKind......................... ....................394 InnerException.................................270, 273
GetUnicodeCategory.......................... ...........174 Inserção ................................................133, 186, 513 Tamanho
ObterNomeUsuário.................................... .............122
da Instância.................................340, 399, 489f.
ObterWindowText................................. ...........372 Memória Inter................................................. .....................84
Global.................................... ...........530 Variáveis Int64.......................... ....................................60
Globais.........................53, 58, 306, 355
Inteiro.......... ................................................34, 60 Auxiliar de
Google.. .................................................. .........526 Ir tipo inteiro..................................61 Pagamento de
para........................................ ...........................98
juros... ...........................................69
Grafemas..................... ...................................167 Interface.... ................................................34f. , 38 Restrições
GUI............... ....................................527f., 530 de interface....................................400 Delegação de
GUID.... ...................................................312, 325, 417
interface..... ...................................321 ID da
interface............ ....................................417 Propriedades da
H Interface....... ...................................320
Interfaces.............. .................310f., 314, 327, 532 Internet das
Parar.................................................. ..................98
Haskell.............................. ................................105 Coisas...................... ............516 Classe
HasWeakRef................. ...................................394 Interposer.......................... ............328, 344 Auxiliares de
Registro Intrínseco......................... ...61 Auxiliares de Tipo
Pilha.............. ...........................................178, 531 HFA
Atributo... ................................................500 Intrínseco..........................350f.
IntToStr................................................. ...........84 Typecast
Alto. ...................................63, 127, 130, 174, 184
inválido.......................... ..........462
HPPGENAtributo..... ....................................500

EU

Comparador................................................408, 410, 419 Invocar......................................... .......................464IO......................... ...........................................2

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 543

Está checado................................................. .........87 Vida................................................. ............58 Vida Útil das


ÉControle......................................... ...................174 Variáveis Locais.............433
ÉDelimitador.......................... .........................186

EstáVazio............ ....................................186
IsInArray............ .............................................175
ÉCarta... .................................................. .........66

LIFO... .................................................. ...........356Linux..............................


Baixo...................................63, 127, 130, 174, 184
Minúsculas.. .................................................. ...186
Lua............................................. ....................200
Lvalor......................... ....................................52

M
ÉLetraOuDígito......................................... .........174ÉInferior........................................ .....................175 IsManagedType........................... ...................394 IsNullOrWhiteSp

Mac OS................................................. 47, 68, 530


J.
MadExceto......................................... ..........262 Malcolm
Java....3, 25, 28, 49, 52, 70, 78, 90, 101, 126, 147, 204, 207, 217, Groves.......................... .........468
228, 234, 239, 247, 256, 276f., 287, 292, 310, 329, 523 Malloc......................................... .......................373 Marco
JavaScript......3, 25, 28, 49, Cantu........................ ..............................6 Dominando
52, 94, 101, 126, 142, 157, 200, 217, 291, 430, 523 Delphi.................. ................................6
Máx................. .................................................. 116
JclDebug........ ....................................................262 João MédiaEDesvPadrão.........................................69 Reprodutor de
Tomás.................................................. ......6 Junte- mídia................................................. ...240
se.......................................... ........................186 Memorando............................................. ....................21
JQuery........................ ....................................431 Vazamento de memória......................... .......................208
JSON........... .................................................. ..516 Manipuladores de Mensagens........................ ...................244
MensagemDlg............................ ........................104
K Metaclasses........................ .............................340
Método............ ....................................105 Encadeamento de
Par de valores-chave............................................. ...384 palavras- Métodos...... ........................................514 Ponteiros de
chave............................................. .............33 Método........ ....................................291
Métodos.......... ...................................144, 205, 532 Aliases de
eu

Rótulo................................................. ...............225 Palavras-


chave de idioma......................... ..........33 Protocolo de Servidor
de Linguagem.........................106
LargeInt... .................................................. ........61
LastIndexOf......................................... .............186 Ligação
Tardia.......................... ..................239 Inicialização
lenta.......................... .......361, 413 Detecção de
Vazamento...................................... ..........368
Comprimento......................................... ................130, 178
Biblioteca......................... ....................................33 Caminho da
Métodos.......... ....................................323Microsoft............ .........................
Biblioteca.............. ...........................................41

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

544 - c: índice

N Divisão.................................................. .............78f.
Mod................................................. .............78
Nome................................................. ......304, 505 Construtores Não................................... ............................78
Nomeados......................................220 Tipos Ou............. .............................................. 78
nomeados.................................................. ....70 Espaços para Shl... .................................................. ...........78
nome......................................... ....38, 43 Shr......................................... ...........................78
NativeInt......................................... .................61
NativeUInt......................... ........................162 Exceções
aninhadas........................ ...................270 Tipos
aninhados.......................... .......................227
NeverSleepOnMMThreadContention............370
Novo........... .................................................. .....162 Nova
Instância........................................... ........374
Herança........................................ ...............532Nick
Hodges................................ ...................309 Niklaus
Wirth.......................... ..............85, 521f.
Nada................................................36, 159 , 209, 361
Notificação............................................. .........411
Agora........................................ ...........................80f.
AgoraUTC.................................................. ...........82
NULO.................................... ...........................159 Declaração
nula.................. ..............................86
CaixaNúmera.................. ....................................89 Tipos Xou..................... ...........................................78:=..... .................................................. .
Numéricos.......... ........................................59
P
O
Pacote................................................. ............34
Objeto................................................. .......35, 532 Inspetor PadEsquerdo.......................... ........................186 Biblioteca de
de Objetos....................293, 296, 298, 300 Modelo de Programação Paralela................444, 516
Referência de Objetos....... ...................207, 357 ParamCount.. .................................................. 499
Programação Orientada a Objetos................200, 202 Parâmetros................................................ ......106 Tipo
Objective-C... ................................25, 78, 147, 157 Paramétrico......................................... .....385
Objetos.......... ...........................................202, 206 Objetos ParamStr......................................... ..............499
como parâmetros.. ...................................358 Pai.................................... ....................218, 342
Estranho............ .................................................. ....63 ParentClass.......................... ...........................340
De.......................................... ...........................34 Do Analisar..................... ....................................62, 186
Objeto.................. ............................292, 296 Pascal......... ...........................................119, 520f.
Ointeres................. ...........................................141 Pedro
Ligado.... .................................................. ..............256
OnChange.......................... .....................342
OnClick........................... ......292f., 296, 304, 440
OnCreate................................... ...............171, 334
OnException......................... ............260, 266
OnMouseDown................................. .............342
POO.................................... ..............................532
Parâmetros de matriz aberta.................. ...................133
Princípio aberto-fechado.......................... ..230, 343
Operador.......................................... ....................
E................................................. ...........78f.
Como................................................. ................78 Wood.................................................. ..........5PHP............................................

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 545

Programa................................................. ...........33 Arquivo Registrar................................................29, 119 , 299


de programa.................................. .........43 Gerente de Registrar Classes................................................ 309
Projetos.............................. .........44, 299 Opções de RegisterComponents..................................299
projeto.........................43f., 73, 533 RegisterExpectedMemoryLeak........ ..............370
Propriedades........................276, 295, 308, 334, 533 Reintroduzir.......................... ..242, 299, 305 Operadores
Propriedades por referência.......... .......................285 Relacionais......................................... 78
Propriedade......................... ...................................36 Liberação.................................................. ..........314f.
Protegido............. ...........................36, 210f., 234f. Remover................................................. ...186, 513
Hack protegido................................................. 235 Repetir......................................... ..............35, 94
Público................................................36, 210f., 287 Substituir......................... .............186, 188, 513
Publicado...............................36, 286ss., 449, 503 ReportMemoryLeaksOnShutdown................369 Repositório
Python..... .................................................. .......157 no GitHub........... ............................5 assemelha-se ao
texto............ ............................189 Exemplo de
P RestoreCursor.......... ................264
Resultado................................ .......................101, 107 Tipo
QualifiedClassName........................................490 de retorno.................... ................................101 Valores de
QueryInterface........ ........................................325 retorno................. ...................................106 Inverter
StringCitada........ ...........................................186 para............. ...........................................91 Cadeia
Citações..... .................................................. ......51f. Inversa..... .............................................189 Aplicações
Robustas.. ....................................374
R Rodada.......... .................................................. ...84
RAD.................................................. ................534 RAD e RTL............................................. .......................39
POO......................... ..................303 Estúdio RTTI.......................... ...286, 288ss., 308, 449, 534 Classes
RAD.......................... ............................4 RTTI................................... ................456
Elevar.................... ..............................36, 256, 261 Rubi................................. ...........................77, 157 Rudy
RaiseOuterException.............. ........................271 Velthuis.................. ...................................69 Biblioteca em
Aleatório........................ ...................................499 tempo de execução.......... ..............................534
Randomizar.............. ........................................499 Erro de Informações sobre o tipo de tempo de
execução......... .............449 Rvalor................................. ...................
verificação de faixa.... ....................................73 Verificação
da faixa......... ....................................126
RawByteString.......... ....................................199 S
Leia.......... ................................36, 276, 509, 511f. Salvar em arquivo................................................. ......515
Propriedade somente leitura..................................277 Escopo......................................... .......................42
Leitores... .................................................. .......510 Enumeradores com Escopo........................ .................74
reais......................................... ....................34, 68 Tela................................. ..............................306
Rebolo.......................... ................................77, 200 SDK.................. ................................................535
Registros............... ........................................34, 534 Auxiliar Selado. .................................................. .............35 Classes
de registro..... ............................344, 347, 351 Auxiliares de Seladas.......................... ...............247 Opções de
Registro............... ................................350 Tipo de pesquisa................................. ..................74 Caminho de
registro............... ....................................137 Registros vs. pesquisa.......................... ...................41, 535
Classes................................................210 registros com
Auto.........................146 , 216f., 291, 306, 332, 361 Ponto e
métodos.... ..............................144 vírgula...................................... ...................86
Recursão................. ................................104, 534 Remetente............................ ........................ Conjunto 295,
RefAtributo.................. ....................................500 440 ...................... ...........................................34, 75 Definir
Referência........... .............................................534 Contagem operadores... ....................................................75
de referência.. ..............................178, 314 Parâmetros de DefinirComprimento.................................................. .......130
referência............ .......................108 Tipos de
SetMinimumBlockAlignment............370
Referência........................ ......................437 SetTimer.............. ...........................................334 Objeto
Reflexão.......................... ......................287, 449 Compartilhado... ................................................529

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

546 - c: índice

Tecla de atalho................................................ ......... Subclassificação.................................................. .....231


Ctrl+/................................................ ...........25 Subintervalo......................................... ................72
Ctrl+C................................... ....................204 SubString................................. .........................186
Ctrl+D.......................... ....................24 Sucesso............ ...........................................63
Ctrl+Shift+C............ ...........................145, 280f. Suporte.... .................................................. .....325
Ctrl+Shift+C............................................... .279 Rápido........................................... ....................242
Ctrl+Shift+G......................................... ...312 Interruptor........................... ....................................89
Ctrl+Shift+L......................................... .......58 Sincronizar.......... ........................................442
Ctrl+Shift+Cima.................................... .......145 Sintaxe........ .................................................. .....24 Destaque
ShortInt.......................................... ...................60 Cadeia de sintaxe......................................... 32
Curta.......................... .........192, 198
MostrarMemória......................... ....................369 T
MostrarMensagem......................... .........................22
GuiaControle.................................................. ......171
Único........................ ...................................34, 68 Padrão
Singleton.......... ...........................315, 338 Hierarquia de classes com
Etiqueta......................................... .................377, 506
TAggregatedObject......................... .....321, 499
raiz única................ .......488 Tamanho......................................... ...................62,
509 TamanhoDe......................... ......62, 137, 141, 394, 399
TBasicAction......................................... ..........503
Fatia.................................. ................................134 TBinárioLeitor...................................... ..........512
SLineBreak................. ............................499, 513 TBinaryWriter.................................... ...........512
SmallInt................. ...........................................60 Conversa
TBits.................................. ...........................502
fiada..... .............................................157, 203 Ponteiros TBufferedFileStream............ .................509 Botão
Inteligentes ................................................420
T................................ ............342, 370, 407
Classificar .................................................. ...............408 Código- TBytesStream................................. .................509
fonte................................. ........................5
TCaráter......................... ........................174
Divisão........................ ........................................186f. TCharHelper........................ ............................174 Classe
T.................. ...........................................341
TColeção...... ................................................502
Colchetes......................................182, 465
Pilha...... .................................................. 261, 535 Estouro TComparador ................................................408, 410
de Pilha............................................. ...104 Biblioteca de TComponent ....................299, 305, 315, 503f.
modelos padrão.....................406 TContainedObject...................................499
StartsWith............. ...........................................186 TCustomAttribute.... ..............................466, 500
Declarações...... .................................................. 85 Matrizes TDataMódulo................ ...................................503
TDate.............. .................................................. 80
Estáticas................................................. .....126 Métodos de
classe estática.........................................333 TDateTime................................80f., 84, 212, 498
Stdcall .................................................. ............119 Tendão TDateTimeHelper....... ...................................82
de Steve................................... ...............383 Atributo TDicionário.......... ...................................406, 416
Armazenado......................... ..............500 TDiretório........... ...........................................507
TEditar.... .................................................. .309, 342 Classes de
Transmissão.................................. ....................506
Fluxos.......................... .................................509 modelo......................................... 383
Estrito............... .................................................. .36 Privado TEncodificação.............................................195f., 199 Arquivo
Estrito............................................. .....210 Estritamente de texto................................................ ............164
Protegido......................................... ......211 TFarquivo.................................. ............................507
TFileStream.................. ................................509
Cadeia......................................... ...............34, 178
TFilterPredicate................ ..............................508
Concatenação de Strings.............................. ...79, 179 String
TFormulário.................. ...........................................250
Helper.......................................... .........186 Listas de
Strings...................................... ................513 TFormatSettings..... ...........................................82
TFunc...... .................................................. ......438
StringRefCount......................... ...............192
StrToDateTime......................... .................81 TGUID.......................................... ...........347, 498
StrToFloat......................... ...........................84 Estrutura de Alça................................... .......................498
THandleStream......................... ....................509
um programa................... .................37

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 547

A Revista Delphi................................304, 328 A Memória TStopWatch.................................................. ....117


Global........... ............................355 A TStream.......................................... ......503, 509
pilha.................. ........................................357 A TStreamReader......................................... .....510f.
Pilha........ ...................................................356 TStreamWriter................................................510f.
OStatus................................................................ ..498 TStringBuilder........................................184, 513f.
Então ............................................... ....................34 TStringList..............................359, 415, 502, 514
Isto.......................... ....................................147 Sincronização TStringReader.......... .......................................510
de threads......... .......................442 TStrings......... ...................................284, 502, 514
Lançamento......................... ....................................256 TStringStream.......... ....................................509
Tempo........... .................................................. .....80 TStringWriter.......... ........................................510
Temporizador........................................... ......................82 TTextLineBreakStyle........ ..............................498
TInterfacedObject........................313ss. , 321, 499 TTextWriter.................. ...................................479
TInterfaceList............................................. ....502 Lista TThread.............. ....................................442, 503
T......................................375, 377, 406f. , 502 TTempo.......... .................................................. ....80
TMemoryManagerEx....................................498 TUnicodeBreak.......................................... ....174 Categoria
TMemoryStream....... ....................................509 Método TUnicode.........................................174 Turbo
T.......... ..............................292, 498 Pascal..................................521f., 531
TMonitor....... .................................................. 498 TValue. .................................................. ..461, 463
TNoRefCountObject.........................................315 TVarData.......................................... ......135, 159
Para...... .................................................. ..............35 TObject. TVarRec......................................... ...................135
0,208, 220, 233, 243, 250, 268, 311, 340, 361, 487f. TVirtualMethodInterceptor.........................471 Classes de
TVisibilidade.................................452, 498
TObjectDictionary.................................406, 412 Tipo......... .................................................. ........34 Aliases de
TObjectList.......... ....................378, 406, 408, 411 tipo........................................ .............352 Tipo
TObjectQueue...................... ....................406, 411 Fundição.......................... ...................83 Compatibilidade de
TObjectStack.......................... .................406, tipo.......................... ..............237 Regras de compatibilidade
411ToCharArray........................... .......................186 de tipo.........................389 Derivação de
ParaInteiro.......................... ................................186 Para tipo................................................231 Inferência de
Inferior.................. ............................66, 175, 186 Paleta de tipo.................................................. ..55 Equivalência de
Ferramentas.............. ...................................299f. nome de tipo..........................71 Promoções de
ToString................................62, 65, 84, 272, 492 tipo..... ........................................152 Parâmetros de matriz
ToUpper....... ....................................66, 175, 186 TP aberta de variante de tipo... .........135 Operadores
Caminho...... ....................................................507 , 509 TypeCast.................................... ..248 Informações do
TPersistente.................................288f., 359, 502f. tipo......................................... ............394
TPointerStream.................................509 TypeScript.................................. ...................202
TProc. .................................................. ............438
TQueue.......................... .......................406 EM

TResourceStream.......................... ..................509 Versão de


teste.......................... ...........................5 UCS4Char..................................67, 169, 174
Corte...................... ...........................................186 UCS4String. .................................................. ...198
TRttiContexto.... ................................454, 457, 460 UInt64............................................. .................60
UIntPtr......................... ...........................161f.
TRttiObject............. ........................................456 Tipo
TRtti........ ................................................455 Operadores Unários...............................................78
Verdadeiro .................................................. .................64 Unicode.................28, 51, 65f., 166, 171, 195, 535 Formatos
Trunço................................. ...................................84 de transformação Unicode............... ...169
Unidade............................................. ................34, 37
Tente.............. ....................................36, 256, 262f.
aulas......................... ...........................510
TenteParse.................................................. ...........62
Genéricos.Coleções.................... ....406f., 411
TSingletonImplementação.....................315, 419
Genéricos.Padrões..............................408, 419
TSmallIntHelper............ .................................351
TStack............... ................................................406 Sistema..... ...............60, 80, 159, 164, 487, 497

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

548 - c: índice

System.Actions........................................515 EM
System.AnsiStrings ....................................515
Sistema.Caráter.......... ..............66f., 174, 515 Var.................................................. ......34, 50, 108
Sistema.Classes........................... 502, 510, 515 Variáveis...................................... .....................50
System.Contnrs.................................406, 515 Variante........................... ...................................135
System.ConvUtils.. ....................................515 Registros de variantes............. .................................140
System.DateUtils......... ........................80, 515 Variantes............... ....................................157, 159
Sistema.Dispositivos.................... .......................515 VCL......... .............21, 39, 503, 506, 522, 528, 536
Diagnóstico.doSistema......................... ......117, Virtuais........................ ....................35, 239ss., 244
515 Sistema.Hash...................................... .........515 Métodos de aula virtual.................... .................332
System.ImageList.................................... ..515 Interceptores de Método Virtual.........................470
System.IniFiles.........................................516 Método Virtual Tabela...................................244
System.IOUtils..................74, 507, 509, 516 Métodos Virtuais........ ....................................536
System.JSON.................. ............................516 Visibilidade.......... ...........................................42, 58
Sistema.Matemática................. .........69, 111, 116, Visual Básico. .................................................. ..142
516 Sistema.Mensagens.............................. .......516 Biblioteca de Componentes Visuais....................21, 39
System.NetEncoding.........................516 Herança de Formulários Visuais............ ............250
System.RegularExpressions. ......................516 ligações visuais ao vivo.......................... .........485
Sistema.Rtti......................... .................454, 516 VmtInstanceSize......................... ...........379
System.StrUtils........................... 173, 189, 516 VmtSelfPtr.................................. ..................379 Atributo
Sistema.SyncObjs........................................516 Volátil.............................. ...............500
Sistema .SysUtils....80, 82, 173, 189, 195, 258, VTipo................................. ..............................159
338, 351, 438, 516
Sistema.Threading...................... ................516 EM
Tipos de sistema......................... ...............516
Referências fracas..................................317, 366
System.TypInfo.................289, 395, 454, 516
WeakAttribute....... ....................................500
System.Variantes... ...................................516
Enquanto....... .................................................. 35, 94f.
Sistema.Zip........ ...........................................516
Espaço em branco.................................................. .......29
Winapi.Windows.... ....................................122
WideChar......................................... ...........65, 67
Nome da Unidade........... .............................................
WideString................................. ...................199
38 Nomes de escopo de unidade ....................................39
Janelas.......................... ....47, 526, 529f., 536 API do
Nome da Unidade.... ..................................................
Windows...................................122 , 245, 333
.490 Tipos Sem Nome......................................... ..70
Com............................................. ............35, 142f.
Inseguro................................................. ...............367
WM_USER................................................. ....244
Referências inseguras......................... ...........317
Palavra............................................ ............60
Atributo Inseguro.................................. .........500
Escrever........................... .............36, 276, 509, 511f.
Até........................................ ...........................35
Escritoras................................................. ............510
UpCase..................... ........................................175
Maiúsculas...... ....................................................28
Maiúsculas.................................................. ......186 X
Tipos de dados definidos pelo usuário....................70 Documento XML.................................................. ...........26
Usos.. .................................................. ...34, 41, 44 Fluxo de XML...................................... ...........478
UTF-16......................................... ..............169, 195
UTF-32.............................. ..............................169 #
UTF-8................ ............................169f., 195, 197
UTF32Char.............. ........................................198 #................................................ ......................176
UTF8String........ ...........................................198f.

€................................................ ..............167, 176

Marco Cantù, Manual do Object Pascal 11


Machine Translated by Google

c: índice - 549

Marco Cantù, Manual do Object Pascal 11

Você também pode gostar