Desenvolvimento

4 set, 2018

Serializando objetos C# com ProtoBuf

Publicidade

Fala, galera!

Antes de começar o artigo, gostaria de compartilhar com vocês que tive a grande honra de me tornar MVP (Microsoft Most Valuable Professional). Com isso, poderei ajudar ainda mais a comunidade com meus artigos, eventos e etc. Deixo o meu muito obrigado a todos que ajudaram de alguma forma, lendo meus artigos, comentando ou indo a eventos nos quais eu ajudei a organizar.

Neste artigo irei falar de uma biblioteca chamada Protobuf.

Essa biblioteca é de extrema valia quando precisamos serializar uma grande quantidade de dados e com uma performance excepcional. Muitas das vezes fazer parse de JSON ou XML não é ideal quando estamos falando de objetos grandes com alguns megas de dados. Neste tipo de cenário uma serialização em formato binário é muito mais eficiente.

A biblioteca Protobuf tem algumas particularidades, como:

  • [ProtoContract]: indica que a classe será serializada usando o Protobuf
  • [ProtoMember]: indica que a propriedade/field deverá ser serializada usando o Protobuf
  • [ProtoIgnore]: indica que a propriedade/field será ignorada quando uma serialização ocorrer

Outras características do Protobuf:

  • A serialização do Protobuf ignora a interface ISerializable
  • Não é possível customizar a serialização do Protobuf usando a interface ISerializable

Show me the code!

Para usar o Protobuf, basta instalar usando o Package Manager Console através do comando abaixo:

Install-Package protobuf-net

Para esse exemplo criei uma classe chamada Cliente e Endereço e anotei com os atributos do Protobuf, conforme no exemplo abaixo:

[ProtoContract()]
public class Cliente
{
    [ProtoMember(1)]
    public string PrimeroNome { get; set; }
    [ProtoMember(2)]
    public string UltimoNome { get; set; }
    [ProtoMember(3)]
    public DateTime DataNascimento { get; set; }
    [ProtoMember(4)]
    public List<Endereco> Enderecos { get; set; }
}
[ProtoContract()]
public class Endereco
{
    [ProtoMember(1)]
    public string Logradouro { get; set; }
    [ProtoMember(2)]
    public string Complemento { get; set; }
    [ProtoMember(3)]
    public string Bairro { get; set; }
    [ProtoMember(4)]
    public string Cidade { get; set; }
    [ProtoMember(5)]
    public string Pais { get; set; }
}

Feito isso, criei uma classe estática que servirá para serializar e desserializar os objetos.

public static class ProtoSerialize
{
       public static byte[] Serialize<T>(T obj) where T : class
       {
           if (obj == null)
               return new byte[] { };
           using(var stream = new MemoryStream())
           {
               ProtoBuf.Serializer.Serialize<T>(stream, obj);
               return stream.ToArray();
           }
       }
       public static T Deserialize<T>(byte[] data) where T : class
       {
           if (data == null)
               return default(T);
           using (var stream = new MemoryStream(data))
           {
               return ProtoBuf.Serializer.Deserialize<T>(stream);
           }
       }
}

Para rodar nosso projeto eu criei um Console Application. Nele temos um teste de desempenho usando a biblioteca Newtonsoft.json, umas das mais famosas bibliotecas para se converter objetos C# em JSON.

static void Main(string[] args)
      {
          var stopWatch = Stopwatch.StartNew();
          for (int i = 0; i < 5000000; i++)
          {
              var obj = CreateCliente(i);
              Newtonsoft.Json.JsonConvert.SerializeObject(obj);
          }
          stopWatch.Stop();
          TimeSpan ts = stopWatch.Elapsed;
          string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
          Console.WriteLine("RunTime Json " + elapsedTime);
          var stopProto = Stopwatch.StartNew();
          for (int i = 0; i < 5000000; i++)
          {
              var obj = CreateCliente(i);
              Protobuf.Serializer.ProtoSerialize.Serialize<Cliente>(obj);
          }
          stopProto.Stop();
          TimeSpan tsProto = stopProto.Elapsed;
          string elapsedTimeProto = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", tsProto.Hours, tsProto.Minutes, tsProto.Seconds, tsProto.Milliseconds / 10);
          Console.WriteLine("RunTime Proto " + elapsedTimeProto);
          Console.Read();
      }
      private static Cliente CreateCliente(int i)
      {
          return new Object.Cliente()
          {
              DataNascimento = DateTime.Now,
              PrimeroNome = quot;Cliente{i}",
              UltimoNome = quot;UltimoNome{i}",
              Enderecos = new List<Endereco>()
              {
                  new Object.Endereco()
                  {
                      Bairro = quot;Bairro{i}",
                      Cidade = quot;Cidade{i}",
                      Complemento = quot;Complemento{i}",
                      Logradouro = quot;Logradouro{i}",
                      Pais = quot;Pais{i}"
                  }
              }
          }; 
      }

A velocidade de serialização dos objetos usando o Protobuf chega a ser 17s mais rápido em relação ao Newtonsoft.json.

Abaixo uma gráfico de comparação de performance em relação a outras bibliotecas.

O código deste artigo está no meu GitHub; você pode acessá-lo clicando aqui.

A documentação do Protobuf pode se encontrada neste link.

Abraços e até a próxima!