Serializing base class properties

244 Views Asked by At

So, if I have:

  [ProtoContract]
  public abstract class BaseRequest
  {
     [ProtoMember(1)] public Guid Guid { get; set; }
  }

  [ProtoContract]
  public class Request : BaseRequest
  {
     [ProtoMember(1)] public long Id { get; set; }
  }

and I try to serialize Request and deserialize BaseRequest, it won't work. It doesn't know what is the concrete class. I would need to add a [ProtoInclude]. That makes sense to me.

What I'm seeing is that if I serialize Request and deserialize Request, that also doesn't work, which I think is unexpected. I would expect the serializer to already know everything it needs in order to work in that case. I need to include the [ProtoInclude] on BaseRequest even if all I'm serializing is Request.

Where I run into trouble is that I have a BaseRequest defined in a library and consumers of that library need to inherit from it. Basically, a particular kind of request has to have data attached to it-- is there a pattern for this, outside of dumping the inheritance and copy/pasting that code into every child class?

2

There are 2 best solutions below

3
On BEST ANSWER

In order to allow you to serialize Request and deserialize BaseRequest, etc, it implements inheritance by starting at the most base type and working out towards more derived types; so if this was xml, it would be:

<BaseType>
    <BaseTypeField1/>
    //...
    <--- at most one, possibly none, of the following -- >
    <SubType1>...</SubType1>
    <SubType2>...</SubType2>
</BaseType>

And it needs to build this understanding of BaseType the first time it tries to touch any of the types in the inheritance model. Now, discovering your base type is easy, but discovering every possible derived type of any type is really hard via reflection, hence why ProtoInclude needs to be on the base types, not the derived types.


If you can provide a reliable, consistent map of field numbers to subtypes, it can all be configured at runtime instead of via ProtoInclude, but: you'd need to provide and manage your own registry of sub-types. If you have this, I can show you how to configure the model in better ways.

1
On

I figured out a way to do it, and I think it'll work, although it certainly feels like there ought to be a better way:

[ProtoContract]
public abstract class BaseRequest
{
   public static int NextSubType = 1000;
   public static ConcurrentDictionary<Type, int> Initialized = new ConcurrentDictionary<Type, int>();
   public static object SyncObject = new object();
 
   [ProtoMember(1)] public Guid Guid { get; set; }

   protected BaseRequest()
   {
      var type = this.GetType();

      if (!Initialized.ContainsKey(type))
      {
         lock (SyncObject)
         {
            if (!Initialized.ContainsKey(type))
            {
               var next = Interlocked.Increment(ref BaseRequest2.NextSubType);
               RuntimeTypeModel.Default.Add(typeof(BaseRequest2), true).AddSubType(next, type);
               Initialized[type] = next;
            }
         }
      }
   }
}

[ProtoContract]
public class Request : BaseRequest
{
   [ProtoMember(1)] public long Id { get; set; }
}