Serialization with protobuf-net
protobuf-net is an open source .net implementation of Google's protocol buffer binary serialization format which I have used as a replacement for the BinaryFormatter
serializer.
Setting it up
Consider the following classes, I have included a sub-class to make it slightly more than a trivial example:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime DateOfBirth { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Number { get; set; }
public string StreetName { get; set; }
}
public class ExtendedAddress : Address
{
public string BuildingName { get; set; }
}
With v2 of protobuf-net
you can build a serializer on the fly which means you don't have to decorate the classes with attributes:
[SetUp]
public void SetUp()
{
var protobufModel = ProtoBuf.Meta.TypeModel.Create();
AddTypeToModel<Person>(protobufModel);
AddTypeToModel<Address>(protobufModel).AddSubType(500, typeof(ExtendedAddress));
AddTypeToModel<ExtendedAddress>(protobufModel);
}
private MetaType AddTypeToModel<T>(RuntimeTypeModel typeModel)
{
var properties = typeof(T).GetProperties().Select(p => p.Name).OrderBy(name => name);//OrderBy added, thanks MG
return typeModel.Add(typeof(T), true).Add(properties.ToArray());
}
While this method is effective it is considered brittle if the class changes. What would happen if we added a Lot
property to the Address
class? This would break the backwards compatibility of any previously serialized data as protobuf-net
is assigning ids to each serialized field.
If we added a new property Suburb
which is alphabetically after StreetName
then backwards compatibility would be maintained albeit by sheer luck.
The moral of the story is only use this method if your class isn't going to change (sure!) or you dont care about backwards compatibility.
A more robust solution is to decorate your classes:
[ProtoContract]
public class Person
{
[ProtoMember(1)]
public string Name { get; set; }
[ProtoMember(2)]
public int Age { get; set; }
[ProtoMember(3)]
public DateTime DateOfBirth { get; set; }
[ProtoMember(4)]
public Address Address { get; set; }
}
[ProtoContract]
[ProtoInclude(500, typeof(ExtendedAddress))]
public class Address
{
[ProtoMember(1)]
public string Number { get; set; }
[ProtoMember(2)]
public string StreetName { get; set; }
}
[ProtoContract]
public class ExtendedAddress : Address
{
[ProtoMember(1)]
public string BuildingName { get; set; }
}
protobuf-net
also respects DataContract
and DataMember
attributes in the System.Runtime.Serialization
namespace if you want to use them instead or if you class is attributed already.
Speed and size
Using AutoFixture I was able to create a Person
object to test serialization with just a couple of lines of code that looks like this:
The following is a summary of speed and size of the serialized data compared to the built-in BinaryFormatter
. Tests conducted on an i7 870 @ 2.93GHz running Windows 7 on .Net4.0 in Release Mode using nunit-console.
Size (bytes) | Speed* (ms) | |
BinaryFormatter | 1024 | 3132 |
protobuf-net | 256 | 458 |
*Serialization time is for 100,000 iterations. 1 iteration is a serialization followed by a deserialization
protobuf-net
is a clear winner in both size (75% smaller) and speed (85% faster)
No new comments are allowed on this post.
Comments
Marc Gravell
Some additional notes:
If using the GetProperties() approach, you must do something to make the order predicatable (for example, sort by name). The CLR makes no guarantees there.
Another simple option is to use "ImplicitFields", i.e.
which will apply serialization to all public fields or properties (XmlSerializer-style), or:
which will apply serialization to all fields (public or private, BinaryFormatter-style) - but either way using protobuf style serialization.