Question

I translate my project to use protobuf-net instead of BinaryFormatter. It looks like there is a lack of documentation http://code.google.com/p/protobuf-net/w/list I also looked up some examples from http://code.google.com/p/protobuf-net/source/browse/ But some things is still not clear for me and that's why i decide to ask here:

1. About ISerializable and Serializer.Merge/Serialize

If we have inheritance from ISerializable to make specific serialization. As i read from: ProtoBuf-Net ProtoInclude Generic Type Subclass we have to use a hook Serializer.Merge/Serialize;

Suppose we have class:

[Serializable]
[ProtoContract]
public class Anchor : ISerializable
{       
    [ProtoMember(1)]
    public int id;

    [ProtoMember(2)]
    public Ship ship;
    ...
 }

the Serializer.Merge(info, this); must be added to constructor Anchor(SerializationInfo info, StreamingContext context)

and Serializer.Serialize(info, this); added to void GetObjectData(SerializationInfo info, StreamingContext context)

So, we have:

    protected Anchor(SerializationInfo info, StreamingContext context)
    {
        //for binaryformatter:            
        Type myType = typeof(Anchor);
        foreach (SerializationEntry e in info)
        {
            FieldInfo f = myType.GetField(e.Name,BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Public|BindingFlags.Instance);
            f.SetValue(this,e.Value);
        }

        //added for protobuf-net:
        Serializer.Merge(info, this);
   }

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        //for binaryformatter:
        foreach (FieldInfo f in typeof(Anchor).GetFields(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance))
        {
            if ((f.Attributes&FieldAttributes.NotSerialized)==0)
                info.AddValue(f.Name,f.GetValue(this));
        }

       //added for protobuf-net:
        Serializer.Serialize(info, this);
    }

Questions: Is this correct? (an "info" is overwrited by Serializer? i.e. binaryformatter will not work properly? at the present point in time i just try to use protobuf-net and l would prefer to leave binaryformatter works properly also)

2. About usage ProtoInclude and RuntimeTypeModel.Default

Suppose we have class Message that is base for classes: class Ack, class HandshakeClientInfo...class Command . If we want to serialize Message's children, as i read from: protobuf-net's [ProtoInclude(1, "MyClass")] did not work we should use a ProtoInclude ("to tell" base class: class Message about it's childrens) in case if we know about children types at compile time - it's ok.

For those childrens which type we cannot determine at compile time (because it is in another project) we should use RuntimeTypeModel.Default[typeof(Message)].AddSubType(207, typeof(Command)); or to use Type.AssemblyQualifiedName: [ProtoInclude(207, "Trainer.Commands.Command, Simulator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]

[Serializable]
[ProtoContract]
[ProtoInclude(200, typeof(Ack))]
[ProtoInclude(201, typeof(HandshakeClientInfo))]
[ProtoInclude(202, typeof(HandshakeReadyToSimulation))]
[ProtoInclude(203, typeof(FileMessage))]
[ProtoInclude(204, typeof(HandshakeResponse))]
[ProtoInclude(205, typeof(Sync))]
[ProtoInclude(206, typeof(HandshakeSimulationStart))]    

public abstract class Message {

    [ProtoMember(1)]
    public byte Sender;
...
}

i use protobuf-net v2 (r580) and the variant with RuntimeTypeModel.Default[typeof(Message)].AddSubType(207, typeof(Command)); seems is more preferable.

Questions: But i don't understand where it should be placed in code? in constructor or.... ? And is it allowable to use ProtoInclude and RuntimeTypeModel.Default together?

Was it helpful?

Solution

First, I need to make very clear the relationship between protobuf-net and ISerializable. Firstly, ISerializable is only used by BinaryFormatter. protobuf-net will never look for this interface, and will never directly use this interface. So, you might ask, why does protobuf-net even mention this?

Answer: because some people have existing code that is required to use BinaryFormatter, but want to use something else internally. A good example of this might be existing code that uses remoting. Here, protobuf-net can be used to implement ISerializable, essentially replacing the guts of BinaryFormatter. Typical usage would be as follows:

protected Anchor(SerializationInfo info, StreamingContext context)
{
    Serializer.Merge(info, this);
}

public virtual void GetObjectData(SerializationInfo info,
    StreamingContext context)
{
    Serializer.Serialize(info, this);
}

This then uses protobuf-net internally; protobuf-net will serialize the object (and any child data), and store the data in a single field; during deserialization it will reverse this. The ISerializable interface is not used to add additional information into protobuf-net. In most cases, if you wanted to separately support protobuf-net and BinaryFormatter, then there would be no Serializer / protobuf-net code in the constructor/GetObjectData, nor would you need to make any changes whatsoever to any existing ISerializable implementation that you have already (or not). That is only for the very specific scenario where you want to change how BinaryFormatter works from the inside. And in that scenario, you would typically hand full control over to protobuf-net.


For your question on sub-types; yes, you can use [ProtoInclude] in combination with RuntimeTypeModel.Default. Basically, by default (it can be tweaked), the first time protobuf-net sees a type, it will check the attributes. If any are there it will use those attributes to configure the model. However, you can still make any changes you need to the configuration until it has to serialize/deserialize that type, at which point it bakes the configuration information into a strategy for doing the work. Once it has decided on a strategy, it doesn't like you changing the configuration.

Therefore, the best time to configure your model is: at app startup. Or at least, before you start doing anything interesting. For completeness, I should note that you can also ask it to ignore the attributes, if you want to configure the model entirely manually (this is pretty rare).

So your line:

RuntimeTypeModel.Default[typeof(Message)].AddSubType(207, typeof(Command));

at app startup would be fine.

In some (rare) cases, you might discover new types much later. For various very complex reasons it isn't very practical to allow changes to an existing model after you've started serializing, but: RuntimeTypeModel.Default is just a default instance. In some more advanced scenarios, what you might want to do is maintain your own model, and then configure a new more knowledgeable one as you need to. So, instead of using RuntimeTypeModel.Default, you might use:

static RuntimeTypeModel serializer = TypeModel.Create();

then at a later time, you can configure a new setup:

var workInProgress = TypeModel.Create();
// TODO: add whatever you need here
serializer = workInProgress;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top