David Hayes
David Hayes

Reputation: 7512

How do you serialize types in an inheritance chain with protobuf-net surrogates?

If you have two (or more) classes in an ineritance chain (GeoCoordinate inherits from PointF2D in this case) how do you use surrogates correctly to allow serialization of either type?

As an example, I have these two surrogate classes

    public class SerializablePointF2D
    {
        [ProtoMember(1)]
        public double[] Values { get; set; }

        public static implicit operator SerializablePointF2D(PointF2D value)
        {
            return value == null ? null : new SerializablePointF2D {Values = value.ToArrayCopy()} ;
        }`enter code here`
        public static implicit operator PointF2D(SerializablePointF2D value)
        {
            return value == null ? null : new PointF2D(value.Values);
        }
    }
    [ProtoContract]
    public class SerializableGeoCoordinate        {
        [ProtoMember(1)]
        public double[] Values { get; set; }

        public static implicit operator SerializableGeoCoordinate(GeoCoordinate value)
        {
            return value == null ? null : new SerializableGeoCoordinate { Values = value.ToArrayCopy() };
        }
        public static implicit operator GeoCoordinate(SerializableGeoCoordinate value)
        {
            return value == null ? null : new GeoCoordinate(value.Values);
        }
    }

And this code setting up the model

        var model = TypeModel.Create();
        //GeoCoordinate
        model.Add(typeof(PrimitiveSimpleF2D), false).AddSubType(1, typeof(PointF2D));
        model.Add(typeof(PointF2D), false).AddSubType(4, typeof(GeoCoordinate)).SetSurrogate(typeof(SerializablePointF2D));            
        model.Add(typeof(GeoCoordinate), false).SetSurrogate(typeof(SerializableGeoCoordinate));

When I attempt to serialize this it gets serialized as a PointF2D rather than a GeoCoordinate. I've tried every combination of ordering I can think of EDIT: Based on Marc's code below I tried

    [ProtoContract]
    public class SerializablePointF2D
    {
        [ProtoMember(1)]
        public double[] Values { get; set; }

        public static implicit operator SerializablePointF2D(PointF2D value)
        {
            if (value == null) return null;
            var geoCoordinate = value as GeoCoordinate;
            if (geoCoordinate != null) return new SerializableGeoCoordinate
            {
                Values = geoCoordinate.ToArrayCopy(),
            };
            return new SerializablePointF2D {Values = value.ToArrayCopy()};
        }
        public static implicit operator PointF2D(SerializablePointF2D value)
        {
            return value == null ? null : new PointF2D(value.Values);
        }
    }
    [ProtoContract]
    public class SerializableGeoCoordinate:SerializablePointF2D
    {

    }

Which I thought looked right. It failed with

System.InvalidOperationException : Unexpected sub-type: OsmSharp.Serialization.OsmSharpSerializer+SerializableGeoCoordinate

Upvotes: 1

Views: 761

Answers (3)

David Hayes
David Hayes

Reputation: 7512

Got it based on both the answers (Thanks!!!)

    [ProtoContract]
    [ProtoInclude(2, typeof(SerializableGeoCoordinate))]
    public class SerializablePointF2D
    {
        [ProtoMember(1)]
        public double[] Values { get; set; }

        public static implicit operator SerializablePointF2D(PointF2D value)
        {
            if (value == null) return null;
            var geoCoordinate = value as GeoCoordinate;
            if (geoCoordinate != null) return new SerializableGeoCoordinate
            {
                Values = geoCoordinate.ToArrayCopy(),
            };
            return new SerializablePointF2D {Values = value.ToArrayCopy()};
        }
        public static implicit operator PointF2D(SerializablePointF2D value)
        {
            if (value == null) return null;
            var geoCoordinate = value as SerializableGeoCoordinate;
            if (geoCoordinate != null)
            {
                return new GeoCoordinate(geoCoordinate.Values);
            }
            return new PointF2D (value.Values );
        }
    }
    [ProtoContract]
    public class SerializableGeoCoordinate:SerializablePointF2D
    {
    }

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1062780

So: you have 3 non-serializable types, and you want to add surrogates for them? Perhaps the trick here is to realise that protobuf-net always goes to the root of any inheritance model - so if a surrogate is declared there: it wins; surrogates take over the serialization/deserialization process completely. The following works by making the surrogates mimic the inheritance of the originals - any use?

using ProtoBuf;
using ProtoBuf.Meta;
using System;
public class A
{
    // we'll use this to detect how it was constructed
    public bool ViaOperator { get; set; }

    public int X { get; set; }
}
public class B : A
{
    public int Y { get; set; }
}
public class C : B
{
    public int Z { get; set; }
}


[ProtoContract, ProtoInclude(1, typeof(BSer))]
public class ASer
{
    [ProtoMember(2)] public int X { get; set; }

    protected virtual A ToA()
    {
        return new A { X = X };
    }

    public static implicit operator A(ASer value)
    {
        if (value == null) return null;
        var a = value.ToA();
        a.ViaOperator = true;
        return a;
    }
    public static implicit operator ASer(A value)
    {
        if (value == null) return null;
        var c = value as C;
        if(c != null) return new CSer {
            X = c.X, Y = c.Y, Z = c.Z};
        var b = value as B;
        if(b != null) return new BSer {
            X = b.X, Y = b.Y };
        return new ASer { X = value.X };
    }
}
[ProtoContract, ProtoInclude(1, typeof(CSer))]
public class BSer : ASer
{
    [ProtoMember(2)] public int Y { get; set; }

    protected override A ToA()
    {
        return new B { X = X, Y = Y };
    }
}
[ProtoContract]
public class CSer : BSer
{
    [ProtoMember(2)] public int Z { get; set; }

    protected override A ToA()
    {
        return new C { X = X, Y = Y, Z = Z };
    }
}

static class Program
{
    static void Main()
    {
        var model = TypeModel.Create();
        model.Add(typeof(A), false).AddSubType(2, typeof(B)).SetSurrogate(typeof(ASer));
        model[typeof(B)].AddSubType(2, typeof(C));

        A obj = new B { X = 1, Y = 2 };

        var clone = (B)model.DeepClone(obj);
        Console.WriteLine("{0}, {1}, {2}", clone.X, clone.Y, clone.ViaOperator);

    }
}

Upvotes: 1

Diego
Diego

Reputation: 18359

Have you tried using ProtoInclude in PointF2D to include GeoCoordinate? Like this:

[Serializable,
ProtoContract(ImplicitFields = ImplicitFields.AllFields, ImplicitFirstTag = 1), 
ProtoInclude(20, "GeoCoordinate")]
public class PointF2D
{
...etc...
}

That should force it to use the surrogate SerializableGeoCoordinate.

Upvotes: 1

Related Questions