I have an enum that is serialized and sent over the wire. Requirements have evolved and I need to support additional values that may be defined in other assemblies, but I can't break binary compatibility for the who-knows-what that is in the wild.
My initial idea was to replace this enum with a enum-like struct wrapping an integer and use a surrogate to serialize it as an integer:
public readonly struct SomeFlag
{
private readonly int _value;
public SomeFlag(int value) { _value = value; }
public static implicit operator int(SomeFlag type)
{
return type._value;
}
public static implicit operator SomeFlag(int value)
{
return new SomeFlag(value);
}
}
...
TypeModel
.Add(typeof(SomeFlag), false)
.SetSurrogate(typeof(int));
When I try to serialize SomeFlag
, I get an error: Data of this type has inbuilt behaviour, and cannot be added to a model in this way: System.Int32
Aside from replacing the structs in my models and code with plain integers, is there any way to make a non-primitive type serialize as a primitive?
Yes, but it requires a custom serializer in v3+ of the library;
using ProtoBuf;
using ProtoBuf.Serializers;
var obj = new HazSomeFlag { Value = new SomeFlag(42) };
var ms = new MemoryStream();
Serializer.Serialize(ms, obj);
if (!ms.TryGetBuffer(out var buffer)) buffer = ms.ToArray();
var hex = BitConverter.ToString(buffer.Array!, buffer.Offset, buffer.Count);
Console.WriteLine(hex); // 08-2A === field 1, varint = 42
ms.Position = 0;
var clone = Serializer.Deserialize<HazSomeFlag>(ms);
Console.WriteLine(clone.Value);
var schema = Serializer.GetProto<HazSomeFlag>();
Console.WriteLine(schema);
[ProtoContract]
public class HazSomeFlag
{
[ProtoMember(1)]
public SomeFlag Value { get; set; }
}
[ProtoContract(Serializer = typeof(MySerializer), Name = "int32")]
public readonly struct SomeFlag
{
public override string ToString() => $"SomeFlag: {_value}";
private readonly int _value;
public SomeFlag(int value) { _value = value; }
public static implicit operator int(SomeFlag type)
{
return type._value;
}
public static implicit operator SomeFlag(int value)
{
return new SomeFlag(value);
}
class MySerializer : ISerializer<SomeFlag>
{
SerializerFeatures ISerializer<SomeFlag>.Features
=> SerializerFeatures.CategoryScalar | SerializerFeatures.WireTypeVarint;
SomeFlag ISerializer<SomeFlag>.Read(ref ProtoReader.State state, SomeFlag value)
=> new SomeFlag(state.ReadInt32());
void ISerializer<SomeFlag>.Write(ref ProtoWriter.State state, SomeFlag value)
=> state.WriteInt32(value._value);
}
}
which works (as shown) compatibly with the schema:
syntax = "proto3";
message HazSomeFlag {
int32 Value = 1;
}