Search code examples
protobuf-net

How to write a custom ISerializer in protobuf-net


I am in the middle of converting serialization in our huge project from BinarySerializers to ProtoBuf, using protobuf-net library.

I have some classes where members have the type object, which needs some custom logic to include the type name before being able to deserialize to member again.

This is quite easy if I just use a surrogate - but I am concerned that this would result in unnecessary many allocations.

I would rather just implement a custom Serializer, which seems to be possible through [ProtoContryt(Serializer=typeof(CustomSerializer)]

Is there a documentation on how to implement such a serializer? In particular the property SerializerFeatures is not very clear to me.


Solution

  • The truth is: this is an advanced API that is not really very external-user-friendly; it is mainly designed for internal usage.

    The SerializerFeatures describes a wide range of flags, but primarily what is needed is a category and a wire-type; the category tells it what we're doing, and the wire-type tells it how we're planning to do it; for most "message" types (meaning: something that can itself have sub-values), you almost certainly want SerializerFeatures.WireTypeString | SerializerFeatures.CategoryMessage, i.e. "this is a 'message', to be serialized using 'length prefix' semantics by default" (although each call-site can override the wire-type preference if needed).

    Some examples can be found in the custom serializer used in protobuf-net.Reflection (which needs to work in cases where ref-emit is not allowed, hence the custom serializer); for example - however, note that most of this code was actually obtained by running the regular protobuf-net ref-emit mechanism to generate a dll, then reverse-engineering the IL from that dll back to C#! I say this as a "if the code looks weird: that's why". I've deliberately linked to a slightly humanized portion of the file, i.e. it uses switch instead of if. But even then it retains some weirdness - for example the string local1 = name; actually comes from a "push, dup, conditional-jump, consume|pop" setup.