C# - Serialização de Objetos - BinaryFormatter e SoapFormatter
Na maioria das vezes que você cria um objeto usando a plataforma .NET você não se preocupa como os dados são armazenados na memória, afinal das contas a plataforma .NET faz esse trabalho para você. |
No entanto, se você deseja
armazenar o conteúdo de um objeto para um arquivo, enviar um
objeto para outro processo, ou transmiti-lo através da rede,
você tem que pensar sobre a forma como o objeto esta
representado, porque você vai precisar
convertê-lo em um formato diferente. Esta conversão é chamado
de serialização.
Neste artigo eu vou escrever sobre o tema serialização para a linguagem C# abordando os seguintes tópicos:
Recursos usados
O que é serialização ?
A Serialização, como implementada no namespace System.Runtime.Serialization, é o processo de serializar e desserializar objetos de modo que eles possam ser armazenados ou transferidos e depois recriados. Temos assim que:
Então se você quiser armazenar um objeto (ou vários objetos) em um arquivo para posterior recuperação, você armazena a saída da serialização, e, na próxima vez que você quiser ler os objetos, você chama os métodos da desserialização, e seu objeto é recriado exatamente como anteriormente.
Da mesma forma, se você quiser enviar um objeto para um aplicativo em execução em outro computador, você estabelece uma conexão de rede, serializa o objeto para o stream e desserializa o objeto na aplicação remota.
Como serializar um Objeto ?
Para serializar um objeto os passos são:
A nível de desenvolvimento, a serialização pode ser implementada com muito pouco código.
A seguir temos um exemplo de uma aplicação Console que usa os namespaces System.IO, System.Runtime.Serialization e System.Runtime.Serialization.Formatters.Binary que demonstra isso:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace SerializacaoCSharp { class Program { static void Main(string[] args) { //serialização C# string dados = "Este texto deve ser armazenado em um arquivo."; // cria um arquivo para salvar os dados FileStream fs = new FileStream("MacorattiSerializacao.Data", FileMode.Create); // Cria um objeto BinaryFormatter para realizar a serialização BinaryFormatter bf = new BinaryFormatter(); // Usa o objeto BinaryFormatter para serializar os dados para o arquivo bf.Serialize(fs, dados); // Fecha o arquivo fs.Close(); //Aguarda o pressionamento de uma tecla para encerrar Console.WriteLine("Arquivo serializado !"); Console.ReadKey(); } } } |
Ao executar o aplicativo será gerado o arquivo MacorattiSerializacao.Data na pasta bin/Debug. Ao abrir este arquivo em um bloco de notas você verá o conteúdo da string armazenada cercada por informações binárias, conforme mostra a figura a seguir:
O. NET Framework armazenou a string como texto ASCII e acrescentou alguns bytes binários antes e depois do texto para descrever os dados para o desserializador.
Se você apenas precisa armazenar uma única seqüência de texto em um arquivo, você não precisa usar a serialização pode simplesmente escrever a string diretamente para um arquivo de texto.
A serialização é útil para armazenar informações mais complexas, como a data e hora atual. A seguir temos um exemplo que demonstra, a serialização de objetos complexos. Note que ela é tão simples como serialização de uma string:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace _2_SerializacaoComplexa { class Program { static void Main(string[] args) { // Serialização C# // Cria um arquivo para salvar os dados FileStream fs = new FileStream("MacorattiSerializaoComplexa.Data", FileMode.Create); // Cria um objeto BinaryFormatter para realizar a serialização BinaryFormatter bf = new BinaryFormatter(); // Usa o objeto BinaryFormatter para serializar os dados para o arquivo bf.Serialize(fs, System.DateTime.Now); // fecha o arquivo fs.Close(); Console.WriteLine("Arquivo serializado !"); Console.ReadKey(); } } } |
Como desserializar um Objeto ?
Desserializar um objeto permite que você crie um novo objeto com base nos dados armazenados. Essencialmente, a desserialização restaura um objeto salvo.
Os passos para desserializar um objeto são:
A nível do código, os passos para desserializar um objeto são fáceis de implementar. A seguir temos uma aplicação Console que usa os namespaces System.IO, System.Runtime.Serialization e System.Runtime.Serialization.Formatters.Binary e mostra como ler e exibir os dados strings serializados guardados no primeiro exemplor:
Obs: Para facilitar eu copei o arquivo serializado para pasta c:\dados => C:\dados\MacorattiSerializacao.Data;
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace _3_Desserializacao { class Program { static void Main(string[] args) { FileStream fs = new FileStream(@"C:\dados\MacorattiSerializacao.Data", FileMode.Open); // Cria um objeto BinaryFormatter para realizar a dessarialização BinaryFormatter bf = new BinaryFormatter(); // Cria um objeto para armazenar os dados dessarializados string dados = ""; // Usa o objeto BinaryFormatter para desserializar os dados do arquivo dados = (string)bf.Deserialize(fs); // fecha o arquivo fs.Close(); // exibe a string desserializada Console.WriteLine(dados); Console.ReadKey(); } } } |
|
Desserializar um objeto complexo é a mesma coisa. Abaixo vemos o código para desserializar o exemplo anterior:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace _4_DesserializacaoData { class Program { static void Main(string[] args) { // Abreo arquivo para ler os dados FileStream fs = new FileStream(@"c:\dados\MacorattiSerializaoComplexa.Data", FileMode.Open); // Cria um objeto BinaryFormatter para realizar a dessarialização BinaryFormatter bf = new BinaryFormatter(); // Cria um objeto para armazenar os dados dessarializados DateTime previousTime = new DateTime(); // Usa o objeto BinaryFormatter para desserializar os dados do arquivo previousTime = (DateTime) bf.Deserialize(fs); // fecha o arquivo fs.Close(); // Exibe o tempo desserializado Console.WriteLine("Dia: " + previousTime.DayOfWeek + ",Hora: " + previousTime.TimeOfDay.ToString()); Console.ReadKey(); } } } |
Como criar classes que possam ser serializadas
Você pode serializar e desserializar classes adicionando o atributo Serializable na classe, dessa forma você ou outros desenvolvedores que usarem a classe poderão armazenar ou transferir instâncias da classe. Mesmo que você no momento não necessite serializar é uma boa prática habilitar suas classes para usar o recurso no futuro. (Aqui cabe o bom senso. Se houver qualquer suspeita de que as classes poderão necessitar de serialização no futuro é bom deixar o terreno preparado.)
Quando a classe for serializada todos os membros, incluídos membros privados serão serializados.
Nota: A serialização pode permitir que outro código modifique ou acesse os dados da instância da classe que de outra forma seriam inacessíveis. Por isso, o código que realiza a serialização necessita do atributo SecurityPermission, do namespace System.SecurityPermission com a flag SerializationFormatter definida. Esta permissão não é dada para código em intranet ou download de internet, somente o código no computador local é concedida a permissão.
Como desabilitar a serialização de membros específicos
Alguns membros de sua classe, como valores temporários ou calculados, pode não precisar serem serializados. Vejamos um exemplo hipotético de uma classe CarrinhoItens usada em um comércio eletrônico:
[Serializable] class CarrinhoItens { public int produtoId; public decimal preco; public int quantidade; public decimal total; public CarrinhoItens(int _produtoID, decimal _preco, int _quantidade) { produtoId = _produtoID; preco = _preco; quantidade = _quantidade; total = preco * quantidade; } } |
[Serializable] class CarrinhoItens { public int produtoId; public decimal preco; public int quantidade; [NonSerialized] public decimal total; public CarrinhoItens(int _produtoID, decimal _preco, int _quantidade) { produtoId = _produtoID; preco = _preco; quantidade = _quantidade; total = preco * quantidade; } } |
A classe CarrinhoItens inclui três membros que devem ser fornecidos pelo aplicativo quando o objeto for criado. O quarto membro, total, é dinamicamente calculado multiplicando-se o preço e quantidade. Se esta classe for serializada como esta, o total seria armazenado com o objeto serializado, desperdiçando uma pequena quantidade de armazenamento.
Para reduzir o tamanho do objeto serializado (e assim reduzir os requisitos de armazenamento quando se escreve o objeto serializado para o disco, e os requisitos de largura de banda durante a transmissão do objeto serializado em toda a rede), adicione o atributo NonSerialized para o membro no total, conforme mostrado no código acima:
Agora, quando o objeto for serializado, o membro total será omitido. Da mesma forma, o membro total não será inicializado quando o objeto for serializado. No entanto, o valor total ainda deve ser calculado antes do objeto serializado ser usado.
Para ativar sua classe para inicializar automaticamente um membro não serializado, utilize a Interface IDeserializationCallback, a seguir, implemente IDeserializationCallback.OnDeserialization. Cada vez que sua classe for serializado, o runtime irá chamar a método IDeserializationCallback.OnDeserialization após a desserialização estar completa.
O exemplo a seguir mostra a classe CarrinhoItens modificada para não serializar o valor total e, para calcular automaticamente o valor na desserialização:
using System; using System.Runtime.Serialization; [Serializable] class CarrinhoItens : IDeserializationCallback { public int produtoId; public decimal preco; public int quantidade; public decimal total; public CarrinhoItens(int _produtoID, decimal _preco, int _quantidade) { produtoId = _produtoID; preco = _preco; quantidade = _quantidade; total = preco * quantidade; } void IDeserializationCallback.OnDeserialization(Object sender) { // depois da serialização , calcula o total total = preco * quantidade; } } |
Com a implementação de OnDeserialization o membor total agora é adequadamente definido e disponível para a aplicação depois da classe ser desserializada.
Como fornecer a compatibilidade de versão
Você pode ter problemas de compatibilidade de versão, se você tentar desserializar um objeto que foi serializado por uma versão anterior da sua aplicação.
Especificamente, se adicionar um membro na classe e tentar desserializar um objeto que não tem esse membro, o runtime irá lançar uma exceção. Em outras palavras, se você adicionar um membro a uma classe na versão x.1 do seu aplicativo, ele não será capaz de desserializar um objeto criado pela versão x.0.
Para superar essa limitação, você tem duas opções:
O atributo OptionalField não afeta o processo de serialização. Durante a desserialização, se o membro não foi serializado, o runtime vai deixar o valor do membro como nulo ao invés de lançar uma exceção. O exemplo seguinte mostra como usar o atributo OptionalField:
[Serializable] Public class CarrinhoItens { public int produtoId; public decimal preco; public int quantidade; [NonSerialized] public decimal total; [OptionalField] public bool ativo;
public CarrinhoItens(int
_produtoID, decimal _preco, int _quantidade) |
Se você precisar iniciar membros opcionais, deverá ou implementar a interface IDeserializationCallback como já comentei ou responder a eventos de serialização que irei tratar mais adiante.
As melhores práticas para a compatibilidade de versão
Para assegurar um comportamento apropriado no versionamento procure seguir as regras abaixo quando alterar uma classe:
Escolhendo o formato da serialização
A plataforma .NET inclui dois métodos para formatar os dados serializados no Namespace System.Runtime.Serialization, ambos implementam a interface IRemotingFormatter:
O formatador SoapFormatter é mais adequado para atravessar com êxito firewalls do que BinaryFormatter.
Resumindo, você deve escolher BinaryFormatter apenas quando você sabe que todos os clientes que irão abrir os dados serializados serão aplicações da plataforma .NET. Portanto, se você for escrever objetos para o disco para serem lidos mais tarde pela sua aplicação, o formato BinaryFormatter é perfeito.
Use SoapFormatter quando outros aplicativos podem ler seus dados serializados e enviar dados através de uma rede. SoapFormatter também funciona de forma confiável em situações onde você pode escolher BinaryFormatter, mas o objeto serializado pode consumir de três a quatro vezes mais espaço.
Como SoapFormatter é baseado em XML, ele destina-se principalmente a ser usado por serviços Web SOAP. Se o seu objetivo é armazenar objetos em um documento aberto, baseado em padrões que pode ser consumido por aplicações rodando em outras plataformas, a maneira mais flexíveis de realizar a serialização é escolher a serialização XML.
Como usar SoapFormatter
Para usar SoapFormatter, adicione uma referência ao assembly System.Runtime.Serialization.Formatters.Soap.dll ao seu projeto. (Ao contrário de BinaryFormatter, ela não está incluído por padrão). Então escreva o código exatamente como você gostaria de usar BinaryFormatter, mas substitua a classe BinaryFormatter pela a classe SoapFormatter.
Escrever código para BinaryFormatter e SoapFormatter é muito parecido mas os dados serializados são muito diferentes. O exemplo a seguir mostra um objeto com 3 membros serializado com SoapFormatter:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <a1:CarrinhoItens id="ref-1"> <produtoId>100</produtoId> <preco>10.25</preco> <quantidade>2</quantidade> </a1:CarrinhoItens> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Exemplo de serialização com SoapFormatter:
No projeto lembre-se de incluir a referência a System.Runtime.Serialization.Formatters.Soap ao projeto;
Selecione o projeto e clique em PROJECT -> Add reference e selecione o assembly conforme abaixo:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Soap; [Serializable] public class Carro { public string Fabricante; public string Modelo; public uint Ano; public byte Cor; } public static class Program { public static int Main(string[] args) { Carro veiculo = new Carro(); veiculo.Fabricante = "Ford"; veiculo.Modelo = "Fiesta Sedan"; veiculo.Ano = 2009; veiculo.Cor = 3; FileStream streamCarro = new FileStream("CarroStream.car", FileMode.Create); SoapFormatter soapCarro = new SoapFormatter(); soapCarro.Serialize(streamCarro, veiculo); return 0; } } |
Note que a única mudança foi usar a classe SoapFormatter ao inves da classe BinaryFormatter.
O arquivo CarroStream.car serializado usando SoapFormatter é vista na pasta bin/debug:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <a1:Carro id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/assem /SoapFormatter%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull"> <Fabricante id="ref-3">Ford</Fabricante> <Modelo id="ref-4">Fiesta Sedan</Modelo> <Ano>2009</Ano> <Cor>3</Cor> </a1:Carro> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Como controlar a serialização SOAP
Podemos controlar a serialização SOAP usando os atributos descritos a seguir:
Atributo | Aplica-se a | Especifica |
SoapAttribute | Campo publico,propriedade, parâmetro ou valor retorno | O membro da classe será serializado como um atributo XML |
SoapElement | Campo publico,propriedade, parâmetro ou valor retorno | A classe será serializada como um elemento XML |
SoapEnum | Campo publico que é um identificador enumeration | O nome do elemento de um membro enumeration |
SoapIgnore | Propriedades e campos públicos | A propriedade ou campo será ignorado quando a classe for serializada |
SoapInclude | Declarações de
classes public derivadas e métodos públicos para documentos WSDL - Web Services Description Language |
O tipo será incluído quando da geração de schemas. |
Os atributos de serialização SOAP funcionam de forma idêntica para os atributos da serialização XML.
Para concluir 3 regras básicas para serialização:
Pegue o projeto completo aqui: SerializacaoCSharp.zip
Mat 6:5 E, quando orardes, não sejais como os hipócritas; pois gostam de orar em pé nas sinagogas, e às esquinas das ruas, para serem vistos pelos homens. Em verdade vos digo que já receberam a sua recompensa.
Mat 6:6 Mas tu, quando orares, entra no teu quarto e, fechando a porta, ora a teu Pai que está em secreto; e teu Pai, que vê em secreto, te recompensará.
Referências: