Reputation: 484
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
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
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
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