Records com Métodos e Propriedades

Você conhece classes e registros na linguagem Delphi. Assim como as classes, records são tipos de dados estruturados definidos pelo usuário. Diferente de classes, no entanto, Records não podem ter métodos e propriedades. Ou podem?

Bem… desde o Delphi 2006 records podem sim ter métodos e propriedades. Mas qual a utilidade? E, afinal, qual é a diferença de um record com métodos e propriedades para uma classe?

A diferença fundamental entre os dois é o método de alocação de memória. Variáveis do tipo “class” (objetos), são na verdade ponteiros para blocos de memória alocados dinamicamente pelo programa. Os dados em si estão em blocos aleatórios gerenciados pelo sistema operacional ou inseridos em um espaço maior reservado pelo compilador (”heap”).

Variáveis do tipo “record”, por outro lado, são alocados sequencialmente da mesma forma que variáveis de tipos primitivos como Integer e Boolean. É alocado na pilha de memória (”stack”), utilizada para a alocação de variáveis locais estáticas e passagem de parâmetros entre subrotinas.

Já ouviu falar em “stack overflow”? É o que acontece quando acaba o espaço nesse bloco de memória compartilhado para variáveis estáticas. Quer dizer que a “pilha” de memória estourou seu limite.

Se voce nunca ouviu falar no termo pilha em programação, entenda de uma vez por todas que não tem nada a ver com baterias. Tem a ver com a idéia de uma pilha de objetos dispostos um sobre o outro, como pratos. O último a entrar (o prato de cima) é sempre o primeiro a sair. É o mesmo esquema com pilhas de memória.

Isso significa que o controle do tempo de vida e a alocação e liberação de memória de records é totalmente automático e seguro, enquanto os objetos (instâncias de classes) precisam ser explicitamente criados (Create) e liberados (Free) pelo programador.

Então qual é a vantagem prática de usar um record? Não precisar se preocupar com ponteiros, alocação e liberação de memória dinâmica. É um objeto com alocação estática.

– “Mas se você tivesse um garbage collector como no .NET não precisaria se procupar com nada disso.

– É. Não precisaria. Mas eu gosto de Win32, então não enche.

Resposta alternativa, mais educada:

– É. Você sempre pode escolher usar o CodeGear Prism, a linguagem Delphi para .NET.

Por ser um bloco de memória estática, um record pode ser passado em parâmetros por valor e por referência e pode ser retornado como resultado de funções, tudo sem a necessidade de preocupar-se com a liberação da memória no momento certo e com a possibilidade de vazamentos de memória (”memory leaks”).

Então vamos um exemplo prático. Digamos que você queira uma estrutura de dados para armazenar os dados do usuário “logado” na aplicação e mover esse conjunto de dados de lá pra cá na sua aplicação quando necessário. Pode usar tanto um record, quanto uma classe (objeto). A declaração do tipo é idêntica, só muda a palavra “record” ou “class”. Observe o código abaixo:

type
  TUsuarioRec = record
  private
    FSenha: String;
    procedure SetSenha(const Value: String);
  public
    Codigo: Integer;
    Nome: String;
    Opcao1: Boolean;
    Opcao2: Boolean;
    constructor Create(OCodigo: Integer; const ONome: String);
    property Senha: String read FSenha write SetSenha;
  end;
 
  TUsuarioObject = class
  private
    FSenha: String;
    procedure AlteraSenha(NovaSenha: String);
    procedure SetSenha(const Value: String);
  public
    Codigo: Integer;
    Nome: String;
    Opcao1: Boolean;
    Opcao2: Boolean;
    constructor Create(OCodigo: Integer; const ONome: String);
    property Senha: String read FSenha write SetSenha;
  end;

Uma limitação que não fica aparente neste exemplo é que records não podem ter métodos virtuais ou destructors. Fora isso, tudo parece igual, não é? A diferença mesmo está na forma de manipular as variáveis. Veja alguns exemplos comparativos:

procedure DoCreateObject;
var
  Usuario: TUsuarioObject;
begin
  Usuario := TUsuarioObject.Create(1, 'José Silva');
  try
    Usuario.Senha := 'novasenha';
  finally
    { o objeto precisa ser desalocado explicitamente }
    Usuario.Free;
  end;
end;
 
procedure DoCreateRecord;
var
  Usuario: TUsuarioRec;
begin
  Usuario.Create(1, 'José Silva');
  Usuario.Senha := 'novasenha';
  { o record é descartado ao sair do escopo }
end;
 
function RetornaObject: TUsuarioObject;
begin
  { Retorna um objeto alocado dinamicamente,
    que precisará ser liberado explicitamente
    por quem chamou a função }
  Result := TUsuarioObject.Create(0, 'Nome');
end;
 
function RetornaRecord: TUsuarioRec;
begin
  { O registro é colocado na pilha de memória e retornado 
     pra uso no escopo de quem chamou a função }
  Result.Create(0, 'Nome');
end;
 
procedure TrataObject(U: TUsuarioObject);
begin
  { altera diretamente o objeto de origem, pois o 
     parâmetro U funciona como um ponteiro }
  U.Opcao1 := True;
end;
 
procedure TrataRecord(U: TUsuarioRec);
begin
  { altera apenas valor do parâmetro, no escopo da procedure }
  U.Opcao1 := True;
end;
 
procedure TrataRecordRef(var U: TUsuarioRec);
begin
  { parâmetro por referência, altera registro de origem }
  U.Opcao1 := True;
end;

Uma curiosidade: lá nos primórdios da programação orientada a objetos no Turbo Pascal (versões 5, 5.5 e 6) usava-se a palavra reservada “object” no lugar de “class” e todos os objetos podiam ser alocados no stack, como os records.

Nova versão de VarStreams

Fiz algumas alterações na unit VarStreams (antiga StreamVars), que faz serialização genérica de Variants em objetos TStream e vice-versa.

Registro de modificações da versão 4:

A última versão do código fonte de VarStreams está disponível para download aqui.

Reordenação de Items em ListBox

Eu tenho uma tabela que contém itens ordenados. A coluna “posicao” indica a posição relativa de cada item (de 1 a n) e seus valores não podem ser repetidos. Eu precisava de uma forma prática de redefinir a ordenação dos itens. A dificuldade é que a cada vez que um item muda de posição todos os itens posteriores também devem mudar.

A solução foi levar todos os nomes de itens para um TListBox respeitando a ordenação atual e deixar o usuário livremente posicionar os nomes sem se preocupar com o dataset. Quando o usuário estiver satisfeito com a ordem dos itens, clica em OK e o programa atribui de uma só vez todos os valores de “posicao” ao dataset conforme os índices dos nomes no ListBox. Read more

Next Page →