Consider the following code example; I want to be able to serialize (which works fine) and deserialize (which doesn't work) the Account
record using protobuf-net:
public abstract record State
{
public abstract ISet<Identity> Identities { get; }
public SerializedState Serialize()
{
using MemoryStream stream = new();
Serializer.Serialize(stream, this);
return new SerializedState(stream.ToArray(), GetType());
}
}
public sealed record Account(Identity Owner, string Identifier, decimal Balance) : State
{
public override ISet<Identity> Identities => new HashSet<Identity> {Owner};
}
The ProtoBuf contract configuration is effectively:
RuntimeTypeModel
.Default
.Add<Account>()
.Add(nameof(Account.Owner))
.Add(nameof(Account.Identifier))
.Add(nameof(Account.Balance));
But I get the following exception:
ProtoBuf.ProtoException: No parameterless constructor found for Example.Account
Is there a way to configure deserialisation (without using attributes) to allow records without parameterless constructors?
The properties of a record are, by default, init
-only, and as such can actually be set by reflection by protobuf-net. Thus your Account
record can be deserialized by protobuf-net by bypassing the constructor as explained in this answer by Marc Gravell to Does protobuf-net support C# 9 positional record types?.
Since you are initializing the contract for Account
in runtime, modify your initialization code as to set MetaType.UseConstructor = false
as follows:
var accountMeta = RuntimeTypeModel
.Default
.Add<Account>()
.Add(nameof(Account.Owner))
.Add(nameof(Account.Identifier))
.Add(nameof(Account.Balance));
accountMeta.UseConstructor = false;
And now you can do:
var account = new Account(identity, "Foo", 1.1m);
var state = account.Serialize();
var account2 = (Account)Serializer.NonGeneric.Deserialize(state.Type, new MemoryStream(state.Data));
Where I assume that SerializedState
looks like:
public class SerializedState
{
public SerializedState(byte [] data, Type type) => (Data, Type) = (data, type);
public byte [] Data { get; set; }
public System.Type Type { get; set; }
}
Demo fiddle here.