Does anyone have a working example where protobuf-net can be used as a drop-in replacement for BinaryFormatter? Is that even possible?
Actually I just need a serializer for one message type which looks like
public class A {
public B[] Bs { get;set; }
public C[] Cs { get;set; }
}
All types are defined in a different assembly and have a lot of properties. Is there an option to automatically generate proto contracts with all public properties included, for class A and other used types (B, C), so something like
var formatter = ProtoBuf.Serializer.CreateFormatter<A>()
just works?
Firstly, protobuf-net is not intended to be, and does not claim to be, a 100% drop in replacement for BinaryFormatter
. It has slightly different features.
Would you be content to do a little reflection? Basically, there is support for ImplicitFields
, but currently this is only available via [ProtoContract]
, and cannot be done conveniently via RuntimeTypeModel
, which is a bit of a pain, and is on my list. Although, I should point out that I consider implicit-fields to be a bit risky, and it should only be done if you know the DTO innards won't change! But: to answer your question, you could iterate over the types you expect, and add them to the model manually:
static void Main()
{
Prepare(typeof(A), typeof(B), typeof(C));
// if you really want to use IFormatter...
var formatter = RuntimeTypeModel.Default.CreateFormatter(typeof (A));
var obj = new A {Bs = new B[] {new B()}};
using (var ms = new MemoryStream())
{
formatter.Serialize(ms, obj);
ms.Position = 0;
var clone = formatter.Deserialize(ms);
}
}
static void Prepare(params Type[] types)
{
if(types != null) foreach(var type in types) Prepare(type);
}
static void Prepare(Type type)
{
if(type != null && !RuntimeTypeModel.Default.IsDefined(type))
{
Debug.WriteLine("Preparing: " + type.FullName);
// note this has no defined sort, so invent one
var props = type.GetProperties();
Array.Sort(props, (x, y) => string.Compare(
x.Name, y.Name, StringComparison.Ordinal));
var meta = RuntimeTypeModel.Default.Add(type, false);
int fieldNum = 1;
for(int i = 0 ; i < props.Length ; i++)
{
meta.Add(fieldNum++, props[i].Name);
}
}
}
Note that the use of IFormatter
here is entirely unnecessary; you could also use RuntimeTypeModel.Default.Serialize(...)
or Serializer.Serialize<T>(...)
.
As a footnote: I would advise defining the models more ... repeatably. For example:
RuntimeTypeModel.Default.Add(typeof(A)).Add("Bs", "Cs");
RuntimeTypeModel.Default.Add(typeof(B)).Add("Foo");
RuntimeTypeModel.Default.Add(typeof(C)).Add("Bar", "Blap", "Blop");
This is the final version of Prepare function I used:
static void Prepare(params Type[] types)
{
foreach (var type in types)
{
if (type != null && !RuntimeTypeModel.Default.IsDefined(type))
{
if (type.Namespace.StartsWith("System"))
return;
Debug.WriteLine("Preparing: " + type.FullName);
// note this has no defined sort, so invent one
var props = type.GetProperties();
Array.Sort(props, (x, y) => string.Compare(
x.Name, y.Name, StringComparison.Ordinal));
var meta = RuntimeTypeModel.Default.Add(type, false);
int fieldNum = 1;
for (int i = 0; i < props.Length; i++)
if (props[i].CanWrite)
{
meta.Add(fieldNum++, props[i].Name);
if (!RuntimeTypeModel.Default.IsDefined(props[i].PropertyType))
if (props[i].PropertyType.HasElementType)
Prepare(props[i].PropertyType.GetElementType()); //T[]
else if (props[i].PropertyType.IsGenericType)
Prepare(props[i].PropertyType.GetGenericArguments()); //List<T>
else
Prepare(props[i].PropertyType);
}
}
}
}
If somebody is interested, this is the result of my benchmark (serialize + deserialize):
BinaryFormatter 10000 messages: 2.131s 5028 bytes/msg
Json.NET Bson 10000 messages: 1.679s 1071 bytes/msg
MetSys Bson 10000 messages: 1.581s 1035 bytes/msg
Protobuf 10000 messages: 0.145s 109 bytes/msg
MsgPack 10000 messages: 0.844s 106 bytes/msg
Notes:
Thanks Marc for your help and such a great library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With