tobeypeters
tobeypeters

Reputation: 484

Creating a descendant of another class

I want to create a new class based off the following:

    [Serializable()]
    public class Class_A : ISerializable
    {
        public int DISP_SEGS { get; set; }

        //Default constructor
        public Class_A()
        {
            //
        }

        //Deserialization constructor
    //If I add virtual to this I get an error
        public Class_A(SerializationInfo info, StreamingContext ctxt)
        {
            foreach (PropertyInfo PI in this.GetType().GetProperties()) PI.SetValue(this, info.GetValue(PI.Name, PI.PropertyType));
        }

        //Serialization function
        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            foreach (PropertyInfo PI in this.GetType().GetProperties()) info.AddValue(PI.Name, PI.GetValue(this));
        }
    }

I want "Class_B" to use the functionality of Serialization & De-Serialization functions. Don't want to create a whole new class and duplicate all the code, which will be in the base. I want to be able to just create a Class_B object and later call:

    Class_B cb = new Class_B();

...

    bformatter.Serialize(stream, cb);

...

    cb = (Class_B)bformatter.Deserialize(stream);

Upvotes: 1

Views: 335

Answers (3)

tobeypeters
tobeypeters

Reputation: 484

What I'm converting:

    //My base class
    [ProtoContract]
    public class grid
    {
        [ProtoMember(1), CategoryAttribute("Grid Settings"), DescriptionAttribute("Number of Horizontal GRID Segments.")]
        public int HorizontalCells { get; set; }

        //more properties
    }

    //Our Protostub ... Originally, just for Stackoverflow.  But, I'm liking the name.
    [ProtoContract]
    [ProtoInclude(7, typeof(SKA_Segments))]
    public class ProtoStub : grid
    {
    }

    SKA_Segments seg_map;

    [ProtoContract]
    public class SKA_Segments : ProtoStub
    {
        public SKA_Segments()
        {
        }

        public override void Draw(Graphics Graf, Pen pencil)
        {
            base.Draw(Graf, pencil);
        }
    }

    //Serialization stuff
    seg_map.HorizontalCells = 1000;  //test property comes from the grid class

    //Need to stream all three of these into the one file
    //Serializer.SerializeWithLengthPrefix(stream, map, PrefixStyle.Base128, 1);
    //Serializer.SerializeWithLengthPrefix(stream, col_map, PrefixStyle.Base128, 1);
    Serializer.SerializeWithLengthPrefix(stream, seg_map, PrefixStyle.Base128, 1);

    //De-Serialization stuff
    //map = Serializer.DeserializeWithLengthPrefix<SKA_MAP>(stream, PrefixStyle.Base128, 1);
    //col_map = Serializer.DeserializeWithLengthPrefix<SKA_Collision>(stream, PrefixStyle.Base128, 1);
    seg_map = Serializer.DeserializeWithLengthPrefix<SKA_Segments>(stream, PrefixStyle.Base128, 1);

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1062855

I'm really answering your answer here, not the question, but:

Yes, protobuf-net models inheritance via the parent type. This is actually pretty common in serialization; see also XmlIncludeAttribute (XmlSerializer) and KnownTypeAttribute (DataContractSerializer). The reason for this is that these serializers don't include the full type metadata into the stream. This is actually a good thing: it means you can change your model or rename your assemblies etc and as long as you still meet the serialization contract: it won't matter. When you think of serialization: yes, it sounds trivial to check the parent types etc, but: for deserialization to work correctly from a polymorphism / LSP standpoint, it needs to be possible to deserialize knowing only the base type.

So: to take your example: if all you knew was ProtoStub, and you saw a "field 7" on the wire, what would you do next? Would you scan all types in the current assembly for something that looks like a candidate? What about types in other assemblies? (there's no requirement that only one assembly is involved in the model). It becomes a very complex task to resolve the sub-types in that scenario.

But: protobuf-net acknowledges that in the multi-assembly you might not have a reference in that direction, so the type can also be specified as a fully-qualified type name as a string, or the entire inheritance model can be configured at runtime via the RuntimeTypeModel API if you prefer. The same is true for some other serializers - for example, XmlSerializer has XmlAttributeOverrides for configuring the model at runtime in a very similar way.

Specifically in the case of protobuf (and protobuf-net), there's one additional reason to prefer decorating the parent type: the field rules. Field numbers (the 7 in [ProtoInclude(7, ...)], or the 3 in [ProtoMember(3)]) are required to be unique for that type, so being able to see the field numbers in use all in one place, rather than needing to look at Class_A / Class_B to see which fields are in use, is very handy. This is because in protobuf terms, the model shown in your example is essentially (in protobuf terms):

message ProtoStub {
  // other fields of ProtoStub here

  oneof subtype {
     Class_A a  = 7;
     Class_B b  = 8;
  }
}
message Class_A {}
message Class_B {}

which is essentially the same as:

message ProtoStub {
  // other fields of ProtoStub here

  optional Class_A a  = 7; // note: at most one of
  optional Class_B b  = 8; // 7/8 will be specified
}
message Class_A {}
message Class_B {}

meaning: the root type contains the sub-types. This seems to surprise most people, who expect the sub-types to contain the parent type, i.e.

message ProtoStub {}
message Class_A {
  // other Class_A fields
  ProtoStub parent = 42;
}
message Class_B {
  // other Class_A fields
  ProtoStub parent = 42;
}

but that layout would make it completely impossible to deserialize when all you know is the root type - how would you know which type you're starting from?

So: that's why it works that way; the logic here can be applied to pretty much every serializer that doesn't embed full type information into the output.


Re the "not working" comment; the most common "not working" thing here is simply not rewinding a MemoryStream between write and read; I wonder if that is what happened in this case. Here's a working example:

using ProtoBuf;
using System;
using System.IO;

public abstract class grid { }

[ProtoContract]
[ProtoInclude(7, typeof(Class_A))]
[ProtoInclude(8, typeof(Class_B))]
public class ProtoStub : grid
{
    [ProtoMember(1)]
    public int RootMember { get; set; }
}

[ProtoContract]
public class Class_A : ProtoStub
{
    [ProtoMember(1)]
    public string DerivedMember { get; set; }
}

[ProtoContract]
public class Class_B : ProtoStub
{
}
static class P
{
    static void Main()
    {
        ProtoStub obj = new Class_A
        {
            RootMember = 123,
            DerivedMember = "abc"
        }, clone;
        using (var ms = new MemoryStream())
        {
            Serializer.Serialize(ms, obj);
            ms.Position = 0;
            clone = Serializer.Deserialize<ProtoStub>(ms);
        }

        Console.WriteLine(clone.RootMember);
        if(clone is Class_A)
        {
            Class_A typed = (Class_A)clone;
            Console.WriteLine(typed.DerivedMember);
        }
    }
}

Upvotes: 0

tobeypeters
tobeypeters

Reputation: 484

As advised, I switched from the Binaryformatter to Protobuf. After, a few issues, I've gotten it working. So, I'm back to my original question in a new format.

I have a base class and want to create a few different classes off it. I seen, you're suppose to use Protobufs ProtoInclude attribute. But, it seems backward. It says, my base class has to know about the derived types.

    [ProtoContract]
    [ProtoInclude(7, typeof(SomeDerivedType))]
    class SomeBaseType {...}

    [ProtoContract]
    class SomeDerivedType {...}

This is a stand-alone class I'm deriving from. So, it needs to be generic.

    [ProtoContract]
    public class grid
    {
    }

So, I need to derive, from it.

    [ProtoContract]
    public class Class_A : grid
    {
    }  

    [ProtoContract]
    public class Class_B : grid
    {
    }

Putting ProtoInclude on Class_A & Class_B doesn't do anything, I understand. Not, if it needs to be on the grid class. What's the best way to do this? As, I'm typing I'm wondering if I need to make a stub class that knows about Class_A & Class_B?

    [ProtoContract]
    [ProtoInclude(7, typeof(Class_A))]
    [ProtoInclude(8, typeof(Class_B))]
    public class ProtoStub : grid
    {
    }

    [ProtoContract]
    public class Class_A : ProtoStub
    {
    }

    [ProtoContract]
    public class Class_B : ProtoStub
    {
    }

If that works, alright. :( But, that's just extra unnecessary code and it makes me sad.

Upvotes: 1

Related Questions