protobuf-net is a .NET library around the Google Protocol Buffers.
For information on the actual Google Protocol Buffers you can checkout the Google documentation.
To get the library via NuGet you can use Install-Package protobuf-net from the Package Manager Console or locate the same from the NuGet UI.
To quote Implementing Google Protocol Buffers using C#, “Protocol Buffers are not designed to handle large messages. If you are dealing in messages larger than a megabyte each, it may be time to consider an alternate strategy. Protocol Buffers are great for handling individual messages within a large data set. Usually, large data sets are really just a collection of small pieces, where each small piece may be a structured piece of data.”
So Protocol Buffers are best used on small sets of of data, but let’s start coding and see how to use Protocol Buffers using protobuf-net.
Time to code
There are a couple of ways of making your classes compliant with the protobuf-net library, the first is to use attributes and the second without attributes, instead setting up the meta data yourself.
Let’s look at an example from the protobuf-net website, a Person class which contains an Address class and other properties.
[ProtoContract] public class Person { [ProtoMember(1)] public int Id { get; set; } [ProtoMember(2)] public string Name { get; set; } [ProtoMember(3)] public Address Address { get; set;} } [ProtoContract] public class Address { [ProtoMember(1)] public string Line1 {get;set;} [ProtoMember(2)] public string Line2 {get;set;} }
As can be seen the classes to be serialized are marked with the ProtoContractAttribute. By default protobuf-net expects you to mark your objects with attributes but as already mentioned, you can also use classes without the attributes as we’ll see soon.
The ProtoMemberAttribute marks each property to be serialized and should contain a unique, positive integer. These identifiers are serialized as opposed to the property name itself (for example) and thus you can change the property name but not the ProtoMemberAttribute number. Obviously, as this value is serialized the smaller the number the better, i.e. a large number will take up unnecessary space.
Serialize/Deserialize
Once we’ve defined the objects we want to serialize with information to tell the serializer the id’s and data then we can actually start serializing and deserializing some data. So here goes. Assuming that we’ve created a Person object and assigned it to the variable person then we can do the following to serialize this instance of the Person object
using (var fs = File.Create("test.bin")) { Serializer.Serialize(fs, person); }
and to deserialize we can do the following
Person serlizedPerson; using (var fs = File.OpenRead("test.bin")) { Person person = Serializer.Deserialize<Person>(fs); // do something with person }
Note: Protocol Buffers is a binary serialization protocol
Now without attributes
As mentioned previously, we might not be able to alter the class definition or simply prefer to not use attributes, in which case we need to setup the meta data programmatically. So let’s redefine Person and Address just to be perfectly clear
public class Person { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set;} } public class Address { public string Line1 {get;set;} public string Line2 {get;set;} }
Prior to serialization/deserialization we would write something like
var personMetaType = RuntimeTypeModel.Default.Add(typeof (Person), false); personMetaType.Add(1, "Id"); personMetaType.Add(2, "Name"); personMetaType.Add(3, "Address"); var addressMetaType = RuntimeTypeModel.Default.Add(typeof(Address), false); addressMetaType.Add(1, "Line1"); addressMetaType.Add(2, "Line2");
as you can see we supply the identifier integer and then the property name.
RuntimeTypeModel.Default is used to setup the configuration details for our types and their properties.
Inheritance
Like the SOAP serialization and the likes, when we derive a new class from a type we need to mark the base type with an attribute telling the serializer what types it might expect. So for example if we added the following derived types
[ProtoContract] public class Male : Person { } [ProtoContract] public class Female : Person { }
we’d need to update our Person class to look something like
[ProtoContract] [ProtoInclude(10, typeof(Male))] [ProtoInclude(11, typeof(Female))] public class Person { // properties }
Note: the identifiers 10 and 11 are again unique positive integers but must be unique to the class, so for example no other ProtoIncludeAttribute or ProtoMemberAttribute within the class should have the same identifier.
Without attributes we simply AddSubType to the personMetaType defined previous, so for example we would add the following code to our previous example of setting up the metadata
// previous metadata configuration personMetaType.AddSubType(10, typeof (Male)); personMetaType.AddSubType(11, typeof(Female)); // and now add the new types RuntimeTypeModel.Default.Add(typeof(Male), false); RuntimeTypeModel.Default.Add(typeof(Female), false);
Alternative Implementations of Protocol Buffers for .NET
protobuf-csharp-port is written by Jon Skeet and appears to be closer related to the Google code using .proto files to describe the messages.