Visual Basic, VB .NET, ASP, Active X, Access, SQL Server

Serialização


     Serialização é uma técnica normalmente usada para gravar objetos em disco, transmití-los via rede, armazená-los no registry ou num banco de dados, mas também pode ser usada para outros propósitos onde uma forma simples e flexível de armazenar informações se fizer necessária. Este artigo lhe mostrará como usar o objeto PropertyBag para fazer com que seus objetos possam se transformar numa matriz de bytes que você poderá gravar em disco, transportar na rede, encriptar, enfim, manipular como bem quiser. Usando o objeto PropertyBag você obtém um bom nível de estruturação e legibilidade no seu código, mas se o que você precisa é apenas velocidade, Variants do tipo matriz ainda são a melhor escolha para armazenar e transmitir o estado de objetos. No final deste artigo falamos sobre Variants tipo matriz.

     O que caracteriza a serialização já está expresso no próprio nome da operação. A serialização consiste em armazenar informações em série. As informações são normalmente armazenadas no formato "Nome da Propriedade/Valor". Antes do VB 6, isto era feito criando-se uma string contendo os pares "Nome da Propriedade/Valor" numa sequência. Com novos recursos adicionados às classes mais a possibilidade de declarar variáveis do tipo PropertyBag, tornou-se mais fácil criar objetos que serializam a si próprios.

Usando o Objeto PropertyBag

Um objeto do tipo PropertyBag pode ser usado para armazenar, propriedade por propriedade, todo o conteúdo de um objeto ou mesmo de uma hierarquia de objetos e recuperar posteriormente todas ou apenas uma destas propriedades. O objeto PropertyBag possui os métodos "WriteProperty", "ReadProperty" e a propriedade "Contents". O método "WriteProperty" é usado para gravar um valor no PropertyBag e associar a ele um nome, enquanto o método "ReadProperty" é usado para recuperar-se um valor gravado com determinado nome. A propriedade "Contents" é do tipo Variant contendo a matriz de bytes dos dados armazenados no objeto PropertyBag. O formato do método "WriteProperty" é : 

 WriteProperty ("Nome da Propriedade", Valor, Default)

onde,  "Nome da Propriedade" é um nome que se quer associar ao valor que está sendo gravado, "Valor" é o valor propriamente dito e "Default" é um valor que, em havendo coincidência com o argumento "Valor", indica que "Valor" não necessita de ser gravado, pois, ao usar o método "ReadProperty" para recuperá-lo,  ficará subentendido o default no caso de não ser encontrado nenhum valor gravado para a propriedade em questão. O mesmo default deverá ser especificado no método ReadProperty para que haja coerência no uso deste parâmetro não obrigatório. Veja abaixo um exemplo de uso do método "WriteProperty".

    Dim pb as PropertyBag

    Dim int_Idade as Integer

    int_Idade= 28

    Set pb = New PropertyBag

    pb.WriteProperty "Idade",  int_Idade, 0%

Após o uso do método "WriteProperty" acima, a propriedade "Contents" do objeto pb terá as informações de int_Idade associadas ao nome "Idade". Estas informações poderão ser recuperadas mediante o uso do método "ReadProperty" do objeto PropertyBag. O método "ReadProperty" possui o seguinte formato:

    ReadProperty ("Nome da Propriedade", Default)

onde, "Nome da Propriedade" é o nome associado a um valor que, supõe-se, tenha sido gravado mediante o uso do método "WriteProperty" e "Default" é um valor que deve ser retornado caso nada seja encontrado associado a "Nome da Propriedade".

Veja abaixo como usar o método "ReadProperty" para recuperar, na mesma variável int_Idade, o valor gravado no exemplo anterior do método "WriteProperty".

   int_Idade = pb.ReadProperty  ("Idade", 0%)

Repare que o valor 0% foi usado como default tanto no método "WriteProperty" como no "ReadProperty". Isto significa dizer que, caso o valor contido na variável int_Idade fosse igual ao default - no caso, 0% - , nada seria gravado, porque ficaria subentendido que o método "ReadProperty" usaria o mesmo default e retornaria o valor 0% quando não encontrasse nenhum valor gravado para a propriedade "Idade". Isto ajuda a poupar espaço na propriedade "Contents" do objeto PropertyBag. Observe que, para garantir isto, fica a cargo do programador especificar o mesmo default tanto no método "WriteProperty" quanto no método "ReadProperty" da propriedade. A não especificação de nenhum valor default no método "ReadProperty" causaria um erro em tempo de execução caso nenhum valor fosse encontrado associado a "Nome da Propriedade".  

Sugestão: defina constantes privativas da classe para os valores a serem usados como default para cada propriedade nos métodos  "WriteProperty" e "ReadProperty".

Uma vez que tenhamos gravado todas as propriedades de um objeto dentro de um PropertyBag, podemos obter os dados desta gravação na forma de um Variant extraído da propriedade "Contents". Veja abaixo como isto é feito.

    Dim varDados as Variant

   varDados = pb.Contents

Desta forma podemos fazer o que quisermos com este Variant: passá-lo como argumento para um método de um objeto remoto e transportá-lo na rede, encriptá-lo,  gravá-lo num arquivo em disco - após atribuí-lo a uma variável do tipo matriz de bytes -, gravá-lo no banco de dados de registro, etc.

Veremos agora dois tipos de possibilidade de se fazer serialização: nas classes de projetos do tipo ActiveX, que passaram a ter a propriedade "Persistable"  a partir do VB 6, o que facilita a serialização, e no caso de projetos onde as classes não têm esta propriedade.

Classes de Projetos Padrão

No caso de estarmos trabalhando com classes de projetos que não sejam do tipo ActiveX, o que fazemos é criar para a classe uma propriedade com o nome, por exemplo, "Serializacao". No procedimento Get da propriedade "Serializacao", instanciamos um objeto do tipo PropertyBag e o utilizamos para gravar todas as propriedades do objeto. Ao final retornamos sua propriedade "Contents" na forma de um Variant. Veja como isto é feito.

Public Property Get Serializacao() as Variant

    Dim pb as PropertyBag

    Set pb = New PropertyBag

    With pb

.WriteProperty "NomePropriedade1", Propriedade1, DefaultPropriedade1

.WriteProperty "NomePropriedade2", Propriedade2, DefaultPropriedade2

    End With

    Serializacao = pb.Contents

End Property  

Para desserializar o objeto, escrevemos o código do procedimento Let da propriedade "Serializacao". Neste caso, recebemos como argumento um Variant que contem todos os dados do objeto gravado anteriormente por uma chamada á propriedade "Serializacao". Veja como isto é feito.

Public Property Let Serializacao ( ByVal varDados as Variant)

    Dim bDados() as Byte, pb as PropertyBag

    bDados = varDados

    Set pb = New PropertyBag

    pb.Contents = bDados

    With pb

Propriedade1 = .ReadProperty ("NomePropriedade1", DefaultPropriedade1)

Propriedade2 = .ReadProperty ("NomePropriedade2", DefaultPropriedade2)

    End With

End Property

Para o caso de uma hierarquia de objetos onde um objeto de mais alto nível deva ser serializado e dentro da sua serialização também se deva ter os objetos nele contidos, deve-se implementar a serialização nas classes dos objetos contidos. Desta forma, pode-se chamar a propriedade "Serializacao" de cada objeto contido e gravar o seu retorno como uma propriedade do objeto de mais alto nível. Veja um exemplo em que o objeto da classe Cliente possui uma propriedade "Pedidos" que se trata de uma coleção de objetos da classe "Pedido".

Public Property Get Serializacao ( ) as Variant

    Dim pb as PropertyBag

    Set pb = New PropertyBag

    With pb 

        'grava as propriedades comuns

.WriteProperty "NomePropriedade1", Propriedade1, DefaultPropriedade1

.WriteProperty "NomePropriedade2", Propriedade2, DefaultPropriedade2

        'grava a coleção de pedidos

        Dim c as Long, i as Long, objPedido as Pedido

        c = Pedidos.Count

        pb.WriteProperty "QuantidadeDePedidos", c, 0

        For i = 1 to c

            Set objPedido = Pedidos(i)

            pb.WriteProperty "Pedido" & CStr(i), objPedido.Serializacao

        Next i

    End With

    Serializacao = pb.Contents

End Property

Para desserializar o objeto, veja a seguir:

Public Property Let Serializacao ( ByVal varDados as Variant) 

    Dim pb as PropertyBag, bDados() as Byte

    bDados = varDados

    Set pb = New PropertyBag

    pb.Contents = bDados

    With pb

        'lê as propriedades comuns

Propriedade1 = .ReadProperty ("NomePropriedade1", DefaultPropriedade1)

Propriedade2 = .ReadProperty ("NomePropriedade2", DefaultPropriedade2)

    End With

    'lê a coleção de pedidos

    Dim c as Long, i as Long, objPedido as Pedido

    c = pb.ReadProperty ("QuantidadeDePedidos", 0)

    For i = 1 To c

        Set objPedido = New Pedido

        objPedido.Serializacao = pb.ReadProperty ("Pedido" & CStr(i))

        Pedidos.Add objPedido

    Next i

End Property

Classes de Projetos do Tipo ActiveX

A partir do VB 6, as classes de projetos do tipo ActiveX DLL e ActiveX Exe passaram a ter mais uma propriedade: "Persistable". Ao escolher o valor desta propriedade como "1-Persistable",  você estará possibilitando que a classe dispare três novos eventos: "InitProperties", "ReadProperties" e "WriteProperties". Estes novos eventos somados à possibilidade de você declarar e instanciar variáveis do tipo "PropertyBag" possibilitam o uso do objeto "PropertyBag" para simplificar o processo de serialização de objetos. 

Configurando a propriedade "Persistable" com o valor "1-Persistable", fica fácil estruturar o código para fazer a serialização do objeto.  Supondo que queiramos serializar um objeto objCliente da classe Cliente. Podemos declarar uma variável como sendo do tipo PropertyBag e chamar o método "WriteProperty" do objeto PropertyBag para gravar de uma só vez o objeto inteiro. Veja como isto é feito.

    Dim pb as PropertyBag

    Set pb = New PropertyBag

   pb.WriteProperty "MeuCliente", objCliente

O que acontece aqui não é nenhuma mágica, porque na verdade ainda teremos que escrever todo o código necessário para gravar o valor de cada uma das propriedades do objeto dentro do PropertyBag. Mas, você deve estar se perguntando: onde diabos eu vou escrever este código? Lembra que falei do evento "WriteProperties" que as classes com a propriedade "Persistable" podem disparar. Pois bem, no exemplo acima, a consequência de você escrever a linha "pb.WriteProperty "MeuCliente",  objCliente" é o disparo do evento "WriteProperties" do objeto objCliente. Este evento tem o argumento de nome "PropBag" que é um objeto do tipo PropertyBag. Sabe qual vai ser o valor deste argumento quando o evento for disparado? Pasme: o objeto pb que está sendo usado para gravar o objeto objCliente na linha: "pb.WriteProperty  'MeuCliente', objCliente". 

Dentro do tratamento do evento "WriteProperty", você poderá usar o método "WriteProperty" do objeto PropBag recebido como argumento do evento para gravar cada propriedade do objeto objCliente. Veja abaixo:

Private Sub Class_WriteProperties(PropBag As PropertyBag)

    With PropBag

.WriteProperties "NomePropriedade1", valorPropriedade1, DefaultPropriedade1

.WriteProperties "NomePropriedade2", valorPropriedade2, DefaultPropriedade2

        .

        .

        .            

    End With


End Sub

Após terminar de tratar o evento "WriteProperties", o objeto pb usado para gravar objCliente conterá na sua propriedade "Contents" todos os dados das propriedades do objeto.

Suponhamos que o valor da propriedade "Contents" de pb é passado para o método "SalvarCliente" de um objeto remoto e tenhamos que desserializar o objeto Cliente e reconstituí-lo na aplicação deste objeto remoto. Vejamos como poderia ficar o método "SalvarCliente":

Public Sub SalvarCliente (ByVal varDados as Variant)

    Dim objCliente as Cliente, bDados() as Byte

    Dim pb as PropertyBag

    Set pb = New PropertyBag

    bDados = varDados

    pb.Contents = bDados

    Set objCliente = pb.ReadProperty ("MeuCliente")

    .

    .

    .

End Sub

O que acontece aqui, mais uma vez, é que a mágica não está completa. Desta vez o que ainda nos resta a fazer é codificar todas as instruções para a leitura de propriedade por propriedade do objeto objCliente contidas em pb. Você talvez já tenha adivinhado onde iremos escrever este código. Claro, dentro do tratamento do evento "ReadProperties" do objeto objCliente. Ao escrever "Set objCliente = pb.ReadProperty ("MeuCliente")" o que você faz é disparar o evento "ReadProperties" do objeto objCliente. Este evento recebe um argumento "PropBag" que nada mais é do que uma referência ao objeto pb. Veja como fica então o tratamento do evento "ReadProperties":

Private Sub Class_ReadProperties(PropBag As PropertyBag)

    With PropBag

  Propriedade1 = .ReadProperty ("NomePropriedade1", DefaultPropriedade1)

  Propriedade2 = .ReadProperty ("NomePropriedade2", DefaultPropriedade2)

        .

        .

        .

    End With

End Sub

Variants tipo matriz

Usando o objeto PropertyBag, você consegue atribuir nomes e valores padrões para as informações que está manipulando. Isto é muito bom, porque garante que você terá liberdade de criar novas versões dos seus objetos que possam ainda entender as versões mais antigas. No entanto, o custo do uso do objeto PropertyBag é uma perda de performance em relação a um outro método de empacotar os dados de um objeto para transmití-los ou armazená-los temporariamente: Variants tipo matriz. Você pode precisar salvar o estado de uma composição antes de iniciar uma macro operação transacionada e, caso algum erro ocorra na operação, você terá como restaurar todo o estado da composição usando os dados salvos. Você pode querer usar estes mesmos dados para transmitir a composição numa rede e restaurá-la em outro computador. Para isto, você pode armazenar todas as propriedades da composição em um único Variant. Crie uma função como a função JuntarDados, exibida abaixo, e passe cada propriedade do objeto na lista de argumentos de JuntarDados. Você terá como retorno um Variant contendo uma matriz com os argumentos passados. Veja a função:

Public Function JuntarDados (ParamArray args() ) As Variant

    Dim ret As Variant

   ret = args

   JuntarDados = ret

End Function

Nesta função, os argumentos são transferidos para uma outra variável para que sejam retornados ByVal. Se você criar um servidor com um método deste tipo e passar os argumentos direto na lista de argumentos, eles serão transmitidos por referência, o que não convém na passagem de dados entre processos.

Na propriedade Serializacao, você poderia retornar os dados do objeto desta forma:

Serializacao = JuntarDados(Nome, Idade, Sexo, Endereco)

Para restaurar os dados, você teria a propriedade Serializacao escrita desta forma:

Public Property Let Serializacao ( NewValue As Variant )

    Nome = NewValue(0)

   Idade = NewValue(1)

   Sexo = NewValue(2)

   Endereco = NewValue(3)

End Property

Uma outra vantagem no uso de Variants tipo matriz sobre o objeto PropertyBag é que podem conter outras matrizes dentro de um elemento, ao passo que o objeto PropertyBag não aceita gravar matrizes como valor de suas propriedades. Por outro lado, usando esta técnica de armazenamento, você estará comprometido a buscá-los na matriz contida no Variant na mesma ordem em que os armazenou, já que não possuem nenhum nome para identificá-los.


E-mail: codelines@codelines.com
Revisada: novembro 20, 2003.