Serialização e desserialização de Objectos em C#

Introdução

Num projecto recente, desenvolvi um sistema multi-tier em que alguns dados eram provenientes de ficheiros XML, e que também era necessário escrever dados para ficheiros em XML. Na definição dos objectos de negócio da aplicação, além das propriedades estavam incluídos dois métodos, ToXML() e FromXML(XmlNode), que convertiam os dados do objecto para XML ou recebia os dados em XML e preenchia o objecto com esses dados.

Inicialmente, os métodos de conversão de XML para objecto e de objecto para XML estavam incluídos na classe que definia o objecto, com a personalização necessária a classe. Esta abordagem era evidentemente má, especialmente quando o numero de classes em que é grande pois não permitia a reutilização do código. Com tempo foi possível desenvolver uma classe generalizada, que permite a serialização e deserialização em XML dos objectos das diversas aplicações que integram a classe, aumentando a eficiência de desenvolvimento.

O artigo que segue, apresenta a estrutura típica em que é usado, a classe XmlCustomSerializer que efectua a serialização e desserialização do objecto automaticamente, e um exemplo de como utilizá-lo.

Enquadramento

As aplicações que tenho desenvolvido, essencialmente na framework .NET (ASP.NET com C#), têm mantido a mesma estrutura multi-camada. Aprendi este método e adaptei-o às minhas necessidades após a leitura de um conjunto de artigos do Imar Spaanjaars (http://imar.spaanjaars.comBuilding Layered Web Applications With Microsoft ASP.NET 2.0).

Resumidamente, o método que ele descreve inclui uma camada de acesso a dados (DAL), uma camada de lógica de negócio (BLL) e a camada de apresentação. Comuns a estas 3 camadas estão os objectos de negócio (BO) que pouco mais são que contentores de objectos – classes definidas apenas com propriedades e, nalguns casos, alguns métodos que possam ser usados nas várias camadas. Podem ser incluídas mais ou menos camadas, que o conceito mantém-se – há a classe de suporte às propriedades, global às camadas, e um conjunto de classes gestoras, em cada camada.

A DAL tem tipicamente o conjunto de métodos CRUD de acesso à base de dados ou repositório de ficheiros. As classes da camada são gestores dos objectos no que diz respeito a acesso a dados. Por exemplo uma classe Pessoa, classe que é um BO com as propriedades de uma pessoa da aplicação, tem na DAL uma classe PessoaDAL, com o conjunto de métodos CRUD para gerir os dados de Pessoa na base de dados ou repositório. A classe tem os métodos Get(), GetList(), Insert(Pessoa), Update(Pessoa), Delete(Pessoa) e outros relevantes.

A BLL é composta também por classes gestoras dos objectos, mas com os métodos relevantes à camada de negócio – validações, autenticação, processamento de dados recebidos da aplicação ou da DAL, etc. Em alguns casos, os métodos da BLL são simples chamadas do método semelhante na DAL. A camada de apresentação tem o conjunto de métodos relevantes à apresentação da informação na aplicação e processamento das acções do utilizador.

Esta estrutura permite tornar transparente a forma como funcionam as diversas camadas. Apenas passa o conjunto de dados gerado pelas camadas para a seguinte, e por exemplo a camada de apresentação nunca terá acesso directo à camada de escrita de dados. Mais, é possível separar e distribuir as diversas camadas através de remoting e/ou web services partilhando recursos entre aplicações ou distribuindo o esforço por diversas máquinas.

Serialização no .NET

A serialização é um mecanismo importante na framework do .NET, em que os objectos são transformados numa stream de dados, formato portável, quer para utilizar remotamente, quer para persistir dados. No caso dos web services os dados das respostas são convertidos para XML, e o objecto a partir desses dados pode ser reconstruído. A serialização e desserialização neste caso é realizada automaticamente pela framework.

Na framework há três modos de serialização:

  • Binária – mais leve e que apresenta a melhor performance e usado no remoting.
  • SOAP – usado por web services para converter os objectos em XML.
  • XML – serialização costumizada para um formato desejado.

Quer a serialização binária, quer a SOAP efectuam Deep Serialization no sentido que serializam o objecto na sua totalidade, incluindo outros objectos contidos nele. Já o XML, porque apenas serializa os elementos públicos do objecto, e é feita da forma como nós indicamos, é chamada de Shallow Serialization.

O resto do artigo está ligado à serialização costumizada em XML. É bastante útil para passar dados que estão em ficheiros XML directamente para um objecto ou vice versa. Há aplicações em que uma base de dados é overkill, e meia dúzia de ficheiros XML resolvem para suportar os dados. Escrever código dedicado a cada objecto é também muitas vezes desnecessário (a menos que segue um formato muito próprio e os objectos em uso não admitem a conversão directa).

Para o demonstrar vou criar um pequeno exemplo. Este servirá para apresentar o modo de implementação dos objectos como contentores, a classe genérica de serialização e a sua aplicação.

Exemplo: lista de contactos

Um bom exemplo é uma lista de contactos. Os objectos de negócio que vou utilizar são:

Serialização em C#: exemplo - objectos de negócio para lista de contactos

A classe Pessoa contém os dados de uma pessoa (ID, nome e contactos) e utiliza ContactoList para associar vários contactos ao mesmo registo de Pessoa. ContactoList não é mais que uma lista genérica do tipo Contacto que contém os dados do contacto individual (tipo de contacto e o valor). PessoaList é também uma lista genérica, mas do tipo Pessoa.

A aplicação exemplo não utiliza as camadas BLL e DAL (a simplicidade não exige). No entanto declarei-os para apresentar um exemplo de como funciona. Como é possível ver no código, a DAL tem o conjunto de métodos que permite aceder à fonte de dados e retornar os objectos. Apenas esta camada tem acesso aos dados, o que torna o processo de obtenção de dados transparente para as camadas superiores. A BLL muitas vezes é uma simples interface para os métodos da DAL, podendo ter mais processamento, como validação e autenticação.

A camada de apresentação é constituída pelos vários formulários que a aplicação pode ter. Estes formulários apenas conhecem os métodos públicos da BLL que por sua vez apenas conhece os métodos públicos da DAL. A separação é implementada através de namespaces, e cada camada conhece apenas a camada seguinte. Este modo implementa uma clara separação da funcionalidade de cada camada.

Relativo à aplicação, a funcionalidade implementada é o mínimo necessário para demonstrar o serializador. Primeiro, tem um campo que permite criar uma instância do objecto Pessoa através do nome. O ID é atribuído automaticamente pelo processo de inserção na lista PessoaList. À última pessoa criada é possível associar uma lista de contactos adicionando um contacto de cada vez. A caixa de texto apresenta o XML gerado pelo serializador a partir da instância do objecto PessoaList que suporta os dados. Por fim, é possível converter os dados do XML em numa nova instância de PessoaList.

A classe Form1 do formulário tem um membro declarado do tipo PessoaList para suportar a lista de pessoas:

public partial class Form1 : Form
{
    PessoaList aLista = new PessoaList();

A adição de uma pessoa cria um objecto pessoa, com o nome preenchido e adiciona-o à lista de pessoas:

private void button1_Click(object sender, EventArgs e)
{
    Pessoa p = new Pessoa(textBox1.Text);
    aLista.Add(p);

    //o ID neste exemplo simples passa a ser a posição na lista
    aLista[aLista.Count-1].ID = aLista.Count;

    //Com o construtor certo, as três linhas de cima poderiam ser escritas numa só, do genéro:
    // aLista.Add(new Pessoa(aLista.Count, textBox1.Text)); //com ID inicial 0

    //SaveList - aqui surgiria a chamada ao processo para armazenar os dados.
    ShowXML();
}

A criação do ID é simulado neste caso – o ID após a inserção na lista é criado a partir do índice do registo na lista. O processo de armazenar os dados também é simulado, apenas mostrando o XML gerado na caixa de texto (o ShowXML será descrito mais à frente.)

Para adicionar contactos temos:

private void button2_Click(object sender, EventArgs e)
{
    Contacto c = new Contacto(textBox2.Text,textBox4.Text);

    if (aLista[aLista.Count - 1].Contactos == null)
           aLista[aLista.Count - 1].Contactos = new ContactoList();

    aLista[aLista.Count - 1].Contactos.Add(c);

    ShowXML();
}

É criado um novo contacto com o par valor / tipo no construtor. Se a lista de contactos da pessoa é nulo, é criado nova instância da lista. Finalmente é adicionado o contacto à lista e armazenada a lista.