4

I'm using Marc Gravell's ProtoBuf-net library (r480, net20) in order to serialize/deserialize a custom class containing a Dictionary<object, object> of known types which is used in a Server/Client scenario (both C#).
This shall replace our current approach using the BinaryFormatter.
As basis I'm following the suggestions made here: protobuf-and-listobject-how-to-serialize-deserialize and here protobuf-and-listobject-how-to-serialize-deserialize.

The current approach has some drawbacks though, i hope that somebody more familiar with Protobuf-net can give me a hint on how to improve it.

  1. Copying of Dictionary<object, object> to Dictionary<ProtoObject, ProtoObject> on the OnSerialising() call.
  2. Maintenance overhead on adding new Types (each requiring an ProtoInclude tag and corresponding cast logic in the ProtoObject.Create(object obj))
  3. All required types must be known by ProtoObject. This causes cyclic reference issues between projects, which only could be resolved by a larger refactoring of the project structure.

Ideally I would like to use the RuntimeTypeModel approach, but i don't see how I can make the client aware of the types (compiling and transmitting the TypeModel dll to the client?).

Also in the first topic, Marc Gravell mentioned that upcoming "runtime-extensible schemas" could help, does anyone know if those are implemented yet and how they work?
I'm very grateful for whatever response I get, please let me know if I can clarify something more.
Anyway, thanks to Marc Gravell for his fantastic library :).

Here's the code:

[Serializable]
[ProtoContract]
public class Attributes : IXmlSerializable, IEnumerable, IEquatable<Attributes>, ICloneable
{
    // Non ProtoBuf-net relevant code was removed

    private Dictionary<object, object> attributes = new Dictionary<object, object>();

    [ProtoMember(1)]
    private Dictionary<ProtoObject, ProtoObject> protoDictionary;

    [OnSerializing]
    public void OnSerializing(StreamingContext context)
    {
        this.protoDictionary = new ProtoDictionary();

        foreach (var attribute in attributes)
        {
            this.protoDictionary.Add(ProtoObject.Create(attribute.Key), ProtoObject.Create(attribute.Value));
        }
    }

    [OnDeserialized]
    public void OnDeserialized(StreamingContext context)
    {
        if (this.protoDictionary != null)
        {
            this.attributes = new SerializableHashtable();

            foreach (var o in this.protoDictionary)
            {
                this.attributes.Add(o.Key.Value, o.Value.Value);
            }
        }
    }
}

[ProtoContract]
[ProtoInclude(1, typeof(ProtoObject<bool>))]
[ProtoInclude(2, typeof(ProtoObject<byte>))]
[ProtoInclude(3, typeof(ProtoObject<sbyte>))]
[ProtoInclude(4, typeof(ProtoObject<ushort>))]
[ProtoInclude(5, typeof(ProtoObject<short>))]
[ProtoInclude(6, typeof(ProtoObject<uint>))]
[ProtoInclude(7, typeof(ProtoObject<int>))]
[ProtoInclude(8, typeof(ProtoObject<ulong>))]
[ProtoInclude(9, typeof(ProtoObject<long>))]
[ProtoInclude(10, typeof(ProtoObject<float>))]
[ProtoInclude(11, typeof(ProtoObject<double>))]
[ProtoInclude(12, typeof(ProtoObject<decimal>))]
[ProtoInclude(13, typeof(ProtoObject<string>))]
[ProtoInclude(20, typeof(ProtoObject<Vector2F>))]
[ProtoInclude(21, typeof(ProtoObject<Vector3F>))]
[ProtoInclude(22, typeof(ProtoObject<Shape>))]
[ProtoInclude(23, typeof(ProtoObject<SharedUser>))]
[ProtoInclude(24, typeof(ProtoObject<SharedShip>))]
//[ProtoInclude(25, typeof(ProtoObject<IVehicleConfiguration>))] // Requires Steering dll -> cyclic reference
[ProtoInclude(26, typeof(ProtoObject<DroneState>))]
[ProtoInclude(27, typeof(ProtoObject<BuffCode>))]
[ProtoInclude(28, typeof(ProtoObject<ItemAttribute>))]
[ProtoInclude(40, typeof(ProtoObject<List<int>>))]
public abstract class ProtoObject
{
    protected ProtoObject()
    {
    }

    // Replaces public static ProtoObject<T> Create<T>(T value)
    // in order to use the actual type of the object
    public static ProtoObject Create(object obj)
    {
        if (obj is bool)
        {
            return new ProtoObject<bool>((bool)obj);
        }

        if (obj is byte)
        {
            return new ProtoObject<byte>((byte)obj);
        }

        // etc. for all required types

        return null;
    }

    public static ProtoObject Create(bool obj)
    {
        TypeModel.Add(obj.GetType(), true);

        return new ProtoObject<bool>(obj);
    }

    public static ProtoObject Create(byte obj)
    {
        return new ProtoObject<byte>(obj);
    }

    // ... public static ProtoObject Create(type obj) -> for all required types

    public object Value
    {
        get { return ValueImpl; }
        set { ValueImpl = value; }
    }

    protected abstract object ValueImpl { get; set; }   
}

[ProtoContract]
public sealed class ProtoObject<T> : ProtoObject
{
    public ProtoObject()
    {
    }

    public ProtoObject(T value)
    {
        Value = value;
    }

    [ProtoMember(1)]
    public new T Value { get; set; }

    protected override object ValueImpl
    {
        get { return Value; }
        set { Value = (T)value; }
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}
Community
  • 1
  • 1

1 Answers1

1

Serializing a Dictionary<object,object> simply isn't a supported use-case... personally, I think you should look more towards using a usage-specific DTO model, as you might if using, say, XmlSerializer, DataContractSerializer or JavascriptSerializer. protobuf-net is still ultimately a contract serializer, with DTO models the ideal use case. Often it works with non-DTO models, but this is not an open guarantee that it will work with every model one can possibly devise.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thank you for your answer Marc. I'm currently implementing a custom serialization and will use Protobuf-net whenever I can. – Marcel Koehler Apr 26 '12 at 14:44