É 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:


using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
Listagem 1. Referência a namespaces necessários

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.


public class CustomBinarySerializer<T>
{

}
Listagem 2. Definição da classe genérica

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


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;
}
Listagem 3. Método para serialização

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:


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;
}
Listagem 4. Método para deserializar

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.


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;
      }
  }
Listagem 5. Código completo da classe

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 atributo [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.


[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;
}
Listagem 6. Alterações no Program.cs

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:


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();
            
}
Listagem 7. Alterações no método Main

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:


TestSerialize classDeserialized = customSer.DeserializeFromBytes(ResultadoSer);
Console.Clear();
Console.WriteLine("ID: {0} - Desc: {1} - Data: {2}", classDeserialized.Cod
classDeserialized.Desc, classDeserialized.DataManut);
Listagem 8. Testando deserialização

Segue o 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();

  }
Listagem 9. Código completo do método Main

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 propriedades 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:


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;
}
Listagem 10. Métodos de serialização para string

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 artigo. 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:


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;
      }
  }
}
Listagem 11. Métodos de criptografia

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:


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);
    
}
Listagem 12. Serialização com criptografia

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:


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();
}
Listagem 13. Novo conteúdo do método Main

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.