I'm upgrading from protobuf-net 2.2.1.0 to 3.2.26, and I've noticed that callback methods decorated with [ProtoBeforeSerialization] and [ProtoAfterDeserialization] are no longer invoked during serialization and deserialization.
In version 2.x, these attributes worked as expected.
I tried explicitly assigning callbacks to the MetaType in a custom RuntimeTypeModel, but that does not work with 3.2.26, although it worked with 2.x.
The callback assignment code used by me and a reproducer program is listed at the end of this topic.
Question:
Is there a breaking change in protobuf-net v3.x that affects callback invocation?
How can I correctly set callbacks in version 3.2.26, especially when using custom inheritance mappings with RuntimeTypeModel?
Below is the helper class that contains the custom RuntimeTypeModel, and it usage to perform serialization and deserialization.
public static class ProtoSerializationHelper
{
private static RuntimeTypeModel Model;
public static byte[] Serialize<T>(T instance)
{
using var ms = new MemoryStream();
CustomSerializer.Serialize(ms, instance);
return ms.ToArray();
}
public static T Deserialize<T>(byte[] payload)
{
using var ms = new MemoryStream(payload);
var obj = (T)CustomSerializer.Deserialize(ms, null, typeof(T));
return obj;
}
public static RuntimeTypeModel CustomSerializer
{
get
{
if (Model == null)
{
Model = RuntimeTypeModel.Create();
//I have also tried using `false` as the value for `applyDefaultBehavior` flag with Model.Add API, doesn't seems have any effect.
var iAnimal = Model.Add(typeof(IAnimal), false);
var animal = Model.Add(typeof(Animal), true);
var dog = Model.Add(typeof(Dog), true);
iAnimal.AddSubType(100, typeof(Animal));
animal.AddSubType(101, typeof(Dog));
animal.SetCallbacks(beforeSerialize: nameof(Animal.OnSerializingCallback), afterSerialize: null, beforeDeserialize: null, afterDeserialize: nameof(Animal.OnDeserializingCallback));
dog.SetCallbacks(beforeSerialize: nameof(Dog.OnSerializingCallback), afterSerialize: null, beforeDeserialize: null, afterDeserialize: nameof(Dog.OnDeserializingCallback));
}
return Model;
}
}
}
Below are the classes that i am trying to (de)serialize.
public interface IAnimal
{
int Id { get; set; }
string Name { get; set; }
DateTime CreatedUtc { get; set; }
}
[ProtoContract]
public class Animal : IAnimal
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; } = string.Empty;
[ProtoMember(3)]
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
internal void OnDeserializingCallback()
{
Console.WriteLine($"[Animal:OnDeserializing Callback] Id={Id}, Name={Name}");
}
internal void OnSerializingCallback()
{
Console.WriteLine($"[Animal:OnSerializing Callback] Id={Id}, Name={Name}");
}
}
[ProtoContract]
public class Dog : Animal
{
[ProtoMember(1)]
public string Breed { get; set; } = string.Empty;
[ProtoMember(2)]
public int BarkVolume { get; set; }
internal void OnDeserializingCallback()
{
Console.WriteLine($"[Dog:OnDeserializing Callback] Id={Id}, Name={Name}");
}
internal void OnSerializingCallback()
{
Console.WriteLine($"[Dog:OnSerializing Callback] Id={Id}, Name={Name}");
}
}
Below is my code that performs (de)serialization.
var dog = new Dog
{
Id = 1,
Name = "Rex",
Breed = "Labrador",
BarkVolume = 7
};
var data = ProtoSerializationHelper.Serialize<IAnimal>(dog);
var clone = ProtoSerializationHelper.Deserialize<IAnimal>(data);
if (clone is Dog deserializedDog)
{
Console.WriteLine($"Dog details -> Id={deserializedDog.Id}, Name={deserializedDog.Name}, Breed={deserializedDog.Breed}, BarkVolume={deserializedDog.BarkVolume}");
}