Serialização Binária em Csharp – Criação de uma Ferramenta de Serialização/Desserialização Genérica

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (0)  (0)

Veja neste artigo os benefícios da utilização deste tipo de serialização em projetos .Net com C#. Entenda as vantagens e desvantagens de utilizar Serialização Binária frente a outros tipos, como a serialização XML.

Olá a todos.

É provável que Serialização Binária não esteja na moda nos tempos atuais, visto que o padrão XML está amplamente divulgado, e com razão, pois oferece muitos benefícios. Porém Serialização Binária é extremamente útil e oferece muitos benefícios.

Para começar, vamos explicar bem resumidamente o que é serialização.

Em linhas gerais serialização é uma técnica utilizada para persistir objetos. É a técnica a ser utilizada quando precisamos gravar objetos oriundos de nossas aplicações em Bancos de Dados, arquivos, ou XML ou então quando precisamos transmitir esses objetos remotamente via rede. Para maiores detalhes, o leitor pode ler este artigo: Overview a respeito de Serialização , onde existe uma explicação um pouco mais detalhada sobre o assunto.

A definição técnica de Serialização Binária segundo a documentação da MSDN é a seguinte:

“...utiliza codificação binária para produzir uma serialização compacta a ser utilizada tanto em armazenamento físico como em streams de rede. Não é adequada para transmitir dados através de Firewall, mas oferece melhor desempenho ao armazenar dados... ”.

Preste atenção na frase final da oração acima, realmente, é a mais pura verdade.

Cheguei a essa conclusão quando estava trabalhando em um projeto onde era necessário serializar um objeto que possuía muitas propriedades do tipo List

Podemos então resumir a vantagem da Serialização Binária com apenas duas palavras: desempenho superior.

Porém, penso que haveria problemas para integrar um sistema feito em .Net com outro feito em Java utilizando esse tipo de serialização, visto que a maneira de implementar Serialização Binária é diferente nesses dois frameworks.

Terminando a parte teórica do artigo, vamos agora construir uma classe que implementará a técnica de Serialização Binária.

No Visual Studio 2010 Express, crie um projeto do tipo Console Application utilizando C#. Nomeie-o como “AppBinSerializer”.

Criando novo projeto

Figura 1: Criando novo projeto

Agora, com o projeto criado, adicione uma nova classe clicando com o botão direito sobre o projeto.

Adicionando nova classe

Figura 2: Adicionando nova classe

Nomeie a classe como CustomBinarySerializer. Essa classe conterá os métodos que receberão um objeto qualquer e o transformará em código binário.

Para começar a codificação adicione os seguintes usings:

Listagem 1: Referência a namespaces necessários

using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

Antes de tudo, vamos deixar nossa classe totalmente genérica colocando um Type Parameter (comando ) na declaração da classe. Isso fará com que possamos serializar qualquer tipo de objeto, mas na instanciação da classe, teremos que indicar os tipos de objetos que iremos serializar.

Listagem 2: Definição da classe genérica

public class CustomBinarySerializer<T>
{

}

Agora criaremos o primeiro método. Ele fará a serialização propriamente dita. Adicione os seguintes comandos à classe:

Listagem 3: Método para serialização

public byte[] SerializeToBytes(T data)
{
    byte[] ret = null;
    if (data != null)
    {
       MemoryStream streamMemory = new MemoryStream();
       BinaryFormatter formatter = new BinaryFormatter();
       formatter.Serialize(streamMemory, data);
       ret = streamMemory.GetBuffer();
    }

    return ret;
}

Simples não? Em linhas gerais criamos um MemoryStream que recebe o retorno do método Serialize do objeto BynaryFormatter. Um detalhe interessante é a declaração do tipo do parâmetro esperado pelo método SerializeToBytes. Repare que utilizamos o Type Parameter declarado na classe, ou seja. Isso significa que, por exemplo, se instanciarmos essa classe passando o tipo String no Type Parameter, esse método automaticamente esperará uma string como parâmetro.

Passaremos agora a codificar o método que realizará a Deserialização, ou seja, transformar um array de byte para um objeto específico. Adicione o seguinte bloco de comando:

Listagem 4: Método para deserializar

public T DeserializeFromBytes(byte[] binData)
{
      T retorno = default(T);
      if (binData != null || binData.Length != 0)
      {
          BinaryFormatter formatter = new BinaryFormatter();
          MemoryStream ms = new MemoryStream(binData);
          retorno = (T)formatter.Deserialize(ms);
      }
      return retorno;
}

Começamos parametrizando o retorno do método de acordo com o Type Parameter da classe. Depois instanciamos um objeto do tipo T utilizando o comando defalt(T). Posteriormente verificamos a array de byte passada como parâmetro para nos certificarmos que ela está preenchida com algum valor e a partir daí utilizamos o método Deserialize do objeto BynaryFormatter, passando um MemoryStream que receberá o array de byte a ser deserializado. Note que fazemos um Parse explícito do retorno do método Deserialize para garantirmos a integridade do tipo retornado pelo método.

É isso. Esses dois métodos implementam a serialização binária. Segue o código completo da classe até aqui.

Listagem 5: Código completo da classe

public class CustomBinarySerializer<T>
    {
        public byte[] Serialize2Bytes(T data)
        {
            byte[] ret = null;

            if (data != null)
            {
                MemoryStream streamMemory = new MemoryStream();
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(streamMemory, data);
                ret = streamMemory.GetBuffer();
            }

            return ret;
        }

        public T DeserializeFromBytes(byte[] binData)
        {
            T retorno = default(T);
            if (binData != null || binData.Length != 0)
            {
                BinaryFormatter formatter = new BinaryFormatter();
                MemoryStream ms = new MemoryStream(binData);
                retorno = (T)formatter.Deserialize(ms);

            }
            return retorno;
        }
    }

Vamos testar nossa aplicação. Primeiramente vamos criar dentro da classes Program uma classe simples com três propriedades e dois construtores somente para testarmos a serialização com tipos complexos (poderíamos testar utilizando tipos nativos do .Net). Repare que precisamos utilizar o atrubuto [Serializable], pois dessa maneira indicamos ao runtime que essa classe pode ser serializada. Sem esse atributo nada irá funcionar.

Dentro da classe Program.cs do Console Application adicione as seguintes linhas de código.

Listagem 6: Alterações no Program.cs

[Serializable]
class TestSerialize
{
   private int _cod;
   private string _desc;
   private DateTime _dataManut;
   public int Cod
   {
        get { return _cod; }
        set { _cod = value; }
   }
   public string Desc
   {
        get { return _desc; }
        set { _desc = value; }
   }
   public DateTime DataManut
   {
       get { return _dataManut; }
       set { _dataManut = value; }
   }
}

public TestSerialize()
{

}

public TestSerialize(int cod, string desc, DateTime data)
{
    this._cod = cod;
    this._desc = desc;
    this._dataManut = data;
}

Agora dentro do método Main vamos instanciar um objeto da classe TestSerialize e instanciar um objeto da classe CustomBinarySerializer. Depois chamamos o método SerializeToByte para serializarmos o objeto TestSerialize e verificaremos o retorno deste método escrevendo o conteúdo do array de byte na tela do console:

Listagem 7: Alterações no método Main

static void Main(string[] args)
{
    TestSerialize classTest = new TestSerialize(1,"TESTE", DateTime.Now);
    CustomBinarySerializer<TestSerialize> customSer = new      CustomBinarySerializer<TestSerialize>();
     byte[] ResultadoSer = customSer.SerializeToBytes(classTest);
     for (int i = 0; i < ResultadoSer.Length; i++)
     {
         Console.WriteLine(ResultadoSer[i]);
     }
     Console.ReadKey();
            
}

Ao rodar o programa o leitor verá no output uma tela como a seguinte:


Resultado da serialização

Figura 3: Resultado da serialização

Veja em modo Debug, que nossa array de bytes referente à serialização esta inteiramente preenchida:

Avaliação do código em execução

Figura 4: Avaliação do código em execução

Agora vamos testar o método de deserialização. Vamos utilizar os comandos do método main escritos até agora e adicionar os seguintes:

Listagem 8: Testando deserialização

TestSerialize classDeserialized = customSer.DeserializeFromBytes(ResultadoSer);
Console.Clear();
Console.WriteLine("ID: {0} - Desc: {1} - Data: {2}", classDeserialized.Cod,       classDeserialized.Desc, classDeserialized.DataManut);

Segue o código completo do método main()

Listagem 9: Código completo do método Main

static void Main(string[] args)
        {
            TestSerialize classTest = new TestSerialize(1, "TESTE", DateTime.Now);
            CustomBinarySerializer<TestSerialize> customSer = new CustomBinarySerializer<TestSerialize>();
            byte[] ResultadoSer = customSer.SerializeToBytes(classTest);

            for (int i = 0; i < ResultadoSer.Length; i++)
            {
                Console.WriteLine(ResultadoSer[i]);
            }

            TestSerialize classDeserialized = customSer.DeserializeFromBytes(ResultadoSer);
            Console.Clear();
            Console.WriteLine("ID: {0} - Desc: {1} - Data: {2}", classDeserialized.Cod, classDeserialized.Desc, classDeserialized.DataManut);

            Console.ReadKey();

        }

Veja o leitor que instanciamos outro objeto da classe TestSerialize que receberá o valor de retorno do método DeserializeFromBytes do objeto CustomBinarySerializer. Depois imprimimos na tela os valore das proprieades deste novo objeto da classe TestSerialize.

Ao rodarmos a aplicação teremos o seguinte output:

Objeto deserializado

Figura 5: Objeto deserializado

Veja em modo Debug que o novo objeto está completamente preenchido com o resultado da deserialização:

Debug da deserialização

Figura 6: Debug da deserialização

Muito bem, vimos até agora como serializar e deserializar objetos com um exemplo totalmente funcional. Poderíamos por exemplo armazenar essa serialização em bytes num Banco de Dados SQL Server em uma tabela que possua uma coluna do tipo Binary e recuperar esses bytes a qualquer momento que quiséssemos.

Poderíamos terminar o artigo por aqui, porém o .Net oferece tanta funcionalidade para melhorarmos essa ferramenta, que seremos obrigados a prosseguir.

Pensemos num cenário hipotético, onde os requisitos de segurança sejam importantes em um determinado projeto. Neste cenário, essa serialização pura de objetos para bytes não é a técnica ideal, pois os bytes podem ser interceptados indevidamente em uma transmissão remota. Quem fizer isso receberá um array bytes que poderá ser facilmente convertido em um objeto, o que significa que as informações do projeto podem estar em risco.

Uma maneira comum de garantir a segurança na transmissão de informações é utilizar algum código de criptografia.

Uma pergunta que poderíamos fazer agora seria – Podemos criptografar array de bytes antes de persistir o objeto? Sim em C# isso é possível. Mas quando falamos em criptografia de conjunto de bytes, podemos pensar em uma criptografia mais comum – a criptografia de strings.

O .Net oferece uma funcionalidade muito interessante de codificar uma array de byte numa representação em cadeia de caracteres. Essa funcionalidade converte um conjunto de bytes em numa string de dígitos “base-64”. Esse tipo de dígito se caracteriza por utilizar caracteres maiúsculos "A" a "Z", as letras minúsculas "a" a "z", os algarismos "0" a "9", e os símbolos "+", "/" e “=”.

Então, sabendo dessa funcionalidade, podemos utilizar nossos métodos de serialização e deserialização que já criamos até aqui, e adicionar trechos de código para transformar as arrays de bytes em strings base-64 e converter essas strings em array de bytes.

Vamos agora criar mais dois métodos na classe CustomBinarySerializer. Adicione os seguintes comandos:

Listagem 10: Métodos de serialização para string

public string SerializeToString(T data)
{
string ret = "";
if (data != null)
       {
       	ret = Convert.ToBase64String(this.SerializeToBytes(data));
}
return ret;
}

public T DeserializeFromString(string binString)
{
T retorno = default(T);
if (binString != null && binString.Length != 0)
{
byte[] binData = Convert.FromBase64String(binString);
retorno = this.DeserializeFromBytes(binData);
}
return retorno;
}

Repare que o método SerializeToString recebe um objeto do tipo T, verifica se esse parâmetro está preenchido e depois chama o método ToBase64String da classe Convert do .Net, passando como parâmetro o retorno do método SerializeToBytes que construímos anteriormente.

Veja que agora em vez de array de bytes o resultado de nossa serialização é uma string. Sendo assim, podemos utilizar qualquer código de criptografia nessa string e armazenar o resultado em uma tabela de Banco de Dados utilizando uma coluna do tipo Varchar, por exemplo.

O método DeserializeFromString recebe uma string base-64, a transforma em um array de bytes utilizando o método FromBase64String da classe Convert do .Net. com o array de bytes montado. Depois chama outro método da própria classe que construímos anteriormente DeserializeFromBytes, que retorna um objeto do tipo T.

Para melhorar nosso exemplo, vou utilizar uma classe para fazer criptografia para efeito de exemplo, portanto não vamos comentar a respeito do método de criptografia, uma vez que não faz parte do escopo do srtigo. O leitor pode ficar a vontade para utilizar qualquer outra forma de criptografia.

Abaixo temos o código da classe com um método para criptografar e outro para descriptografar:

Listagem 11: Métodos de criptografia

public class Crypto
    {
        private byte[] key = { };
        private byte[] IV = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef };

        private const string ENCRYPTIONKEY = "t3ebmbr4";
        
        public string Decrypt(string stringToDecrypt)
        {
            byte[] inputByteArray = new byte[stringToDecrypt.Length + 1];
            try
            {
                key = System.Text.Encoding.UTF8.GetBytes(ENCRYPTIONKEY);
                DESCryptoServiceProvider des = new DESCryptoServiceProvider();
                inputByteArray = Convert.FromBase64String(stringToDecrypt);
                MemoryStream ms = new MemoryStream();
                CryptoStream cs = new CryptoStream(ms,des.CreateDecryptor(key, IV), CryptoStreamMode.Write);
                cs.Write(inputByteArray, 0, inputByteArray.Length);
                cs.FlushFinalBlock();
                System.Text.Encoding encoding = System.Text.Encoding.UTF8;
                return encoding.GetString(ms.ToArray());
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }

        public string Encrypt(string stringToEncrypt)
        {
            try
            {
                key = System.Text.Encoding.UTF8.GetBytes(ENCRYPTIONKEY);
                DESCryptoServiceProvider des = new DESCryptoServiceProvider();
                byte[] inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
                MemoryStream ms = new MemoryStream();
                CryptoStream cs = new CryptoStream(ms,
                  des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
                cs.Write(inputByteArray, 0, inputByteArray.Length);
                cs.FlushFinalBlock();
                return Convert.ToBase64String(ms.ToArray());
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }
    }

Agora podemos utilizar os métodos SerializeToString e DeserializeFromString para implementarmos nossa criptografia.

Vamos criar mais dois métodos de serialização, utilizando agora a classe de Criptografia:

Listagem 12: Serialização com criptografia

public string SerializeToEncryptedString(T data)
        {
            string ret = "";
            Crypto crypter = new Crypto();
            ret = crypter.Encrypt(this.SerializeToString(data));
            return ret;
        }

        public T DeserializeFromEncryptedString(string binStringEncrypted)
        {
            Crypto crypter = new Crypto();
            string strDecrypted = crypter.Decrypt(binStringEncrypted);
            return this.DeserializeFromString(strDecrypted);
            
        }

Perceba que no primeiro método, utilizamos o método encrypt do objeto Crypto para criptografar a string retornada pelo método SerializeToString, que construímos anteriormente. Já no segundo método utilizamos o método Decrypt para descriptografar uma string para poder utilizá-la no método DeserializeFromString que criamos anteriormente.

Vamos testar então essas novas funcionalidades que acabamos de implementar.

Na classe program, vamos reescrever o método main. Vamos apagar tudo o que foi escrito até agora e adicionar os seguintes códigos:

Listagem 13: Novo conteúdo do método Main

static void Main(string[] args)
        {
            TestSerialize objToSerialize = new TestSerialize(2, "TESTE", DateTime.Now);
            CustomBinarySerializer<TestSerialize> customSer = new CustomBinarySerializer<TestSerialize>();

            string strEncryptedSerialization = customSer.SerializeToEncryptedString(objToSerialize);

            Console.WriteLine("==================Serialização com Criptografia=====================");
            Console.WriteLine("Resultado da Serialização Criptografada:");
            Console.WriteLine();
            Console.WriteLine(strEncryptedSerialization);
            
            TestSerialize objToDeserialize = new TestSerialize();
            objToDeserialize    = customSer.DeserializeFromEncryptedString(strEncryptedSerialization);

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("==================Deserialização com Criptografia=====================");
            Console.WriteLine("Objeto deserializado:");
            Console.WriteLine("Codigo: {0}", objToDeserialize.Cod);
            Console.WriteLine("Desc: {0}", objToDeserialize.Desc);
            Console.WriteLine("Data: {0}", objToDeserialize.DataManut);

            Console.ReadKey();
        }

Basicamente, estamos instanciando uma string e dois objetos da classe TestSerialize para utilizar os retornos dos métodos de serialização e deserialização com criptografia da classe CustomBinarySerializer.

Ao rodar a aplicação teremos o seguinte output:

Resultado daserialização criptografada

Figura 7: Resultado daserialização criptografada

Acho que essa serialização é muito interessante, pois nos pois nos proporciona muita agilidade no processo de serialização de objetos extremamente pesados.

Fica aqui a dica aos leitores de mais essa técnica que pode substituir a serialização XML, ou então trabalhar em conjunto.

Espero que esse artigo seja útil. Qualquer dúvida ou crítica, o leitor fique a vontade de deixar seus comentários.

Obrigado a todos.

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?