Dev (Back & Front)ARTIGO

Serializando objetos C# com ProtoBuf

Neste artigo, o autor apresenta a biblioteca Protobuf e demonstra um processo de serialização de objetos C# através dela.

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 = $"Cliente{i}",
              UltimoNome = $"UltimoNome{i}",
              Enderecos = new List<Endereco>()
              {
                  new Object.Endereco()
                  {
                      Bairro = $"Bairro{i}",
                      Cidade = $"Cidade{i}",
                      Complemento = $"Complemento{i}",
                      Logradouro = $"Logradouro{i}",
                      Pais = $"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!

Rafael CruzAutor

Rafael Cruz é Partner e Lead Software Architect na Electric Dreams, Professor Universitário, Most Valuable Profissional (MVP), Instrutor Oficial Microsoft (MCT), Fundador do Coders in Rio, Pós graduado em Engenharia de Software pela UFRJ. Atua como palestrante e autor em portais voltado para tecnologia Microsoft com foco em Web, Cloud e Mobile

Ver perfil