protobuf-net 是否支持 C# 9 位置记录类型?

Does protobuf-net support C# 9 positional record types?

我正在尝试将 protobuf-net 与 C# 位置记录类型一起使用,但我遇到了这个异常:

10:18:48.048 [EROR] #010 (Microsoft.AspNetCore.Server.Kestrel) Connection id ""0HM4NDHMUB3C6"", Request id ""0HM4NDHMUB3C6:00000003"": An unhandled exception was thrown by the application.
Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Error starting gRPC call. ProtoException: No parameterless constructor found for Bidirectional.Demo.Common.Contracts.Server.GetServerProcessI
nfo.GetServerProcessInfoResponse", DebugException="ProtoBuf.ProtoException: No parameterless constructor found for Bidirectional.Demo.Common.Contracts.Server.GetServerProcessInfo.GetServerProcessInfoResp
onse
   at ProtoBuf.Internal.ThrowHelper.ThrowProtoException(String message, Exception inner) in /_/src/protobuf-net.Core/Internal/ThrowHelper.cs:line 70
   at ProtoBuf.Meta.TypeModel.ThrowCannotCreateInstance(Type type, Exception inner) in /_/src/protobuf-net.Core/Meta/TypeModel.cs:line 1666
   at proto_12(State& , GetServerProcessInfoResponse )
   at ProtoBuf.Internal.Serializers.SimpleCompiledSerializer`1.ProtoBuf.Serializers.ISerializer<T>.Read(State& state, T value)
   at ProtoBuf.ProtoReader.State.ReadAsRoot[T](T value, ISerializer`1 serializer)
   at ProtoBuf.ProtoReader.State.DeserializeRoot[T](T value, ISerializer`1 serializer)
   at ProtoBuf.Meta.TypeModel.Deserialize[T](ReadOnlySequence`1 source, T value, Object userState)
   at ProtoBuf.Grpc.Configuration.ProtoBufMarshallerFactory.ContextualDeserialize[T](DeserializationContext context)

这是 GetServerProcessInfoResponse 的样子:

[ProtoContract]
public record GetServerProcessInfoResponse(
    [property: ProtoMember(1)] TimeSpan TotalProcessorTime,
    [property: ProtoMember(2)] TimeSpan UserProcessorTime,
    [property: ProtoMember(3)] TimeSpan PrivilegedProcessorTime,
    [property: ProtoMember(4)] string CurrentMemoryUsage,
    [property: ProtoMember(5)] string PeakMemoryUsage,
    [property: ProtoMember(6)] int ActiveThreads
);

如果我将 GetServerProcessInfoResponse 更改为具有可获取和可设置属性的常规 C# class,代码可以正常工作。然而,我希望记录也能起作用,因为它们避免了大量的可空性警告。 System.Text.Json 支持反序列化记录,例如,它必须针对相同的限制工作。

我在文档、问题或此处 Whosebug 上找不到任何内容,所以可能我不擅长搜索,或者答案还没有出来。 :-)

protobuf-net 存储库似乎也没有任何尝试序列化/反序列化 c# 记录的单元测试,它只包含“RecordTypeTests”,它似乎在检查记录是否可以被克隆?

是的,有两种不同的方式。

使用您的 现有 代码,最简单的选择是告诉 protobuf-net 执行巫术以克服没有可用构造函数的事实;幸运的是,这很简单:

[ProtoContract(SkipConstructor = true)]
public record GetServerProcessInfoResponse(
    [property: ProtoMember(1)] TimeSpan TotalProcessorTime,
    [property: ProtoMember(2)] TimeSpan UserProcessorTime,
    [property: ProtoMember(3)] TimeSpan PrivilegedProcessorTime,
    [property: ProtoMember(4)] string CurrentMemoryUsage,
    [property: ProtoMember(5)] string PeakMemoryUsage,
    [property: ProtoMember(6)] int ActiveThreads
);

但是!对于最近构建的 v3 代码,它还会 推断 您正在尝试做什么 特别是在纯位置记录的情况下 ,即以下内容也应该工作,并且意味着 或多或少 相同的东西(作为一个细节:在这种情况下它实际上会使用构造函数,而不是通过 voodoo 创建实例然后踩踏值):

public record GetServerProcessInfoResponse(
    TimeSpan TotalProcessorTime,
    TimeSpan UserProcessorTime,
    TimeSpan PrivilegedProcessorTime,
    string CurrentMemoryUsage,
    string PeakMemoryUsage,
    int ActiveThreads
);

Records 会起作用,正如此处的评论和 Marc 的回答所示,问题出在构造函数上,而不是它是一个记录类型。

记录类型只是 类,带有编译器生成的代码和属性。

要显示到达所需位置的不同方式,您可以只添加无参数构造函数。是的,它比简单的记录声明要多一些工作,但是如果你坚持使用不支持参数化构造函数的 ProtoBuf 版本,并且你不想像 Marc 展示的那样绕过构造函数,这里有一个不同的选项:

[ProtoContract]
public record GetServerProcessInfoResponse(
    [property: ProtoMember(1)] TimeSpan TotalProcessorTime,
    [property: ProtoMember(2)] TimeSpan UserProcessorTime,
    [property: ProtoMember(3)] TimeSpan PrivilegedProcessorTime,
    [property: ProtoMember(4)] string CurrentMemoryUsage,
    [property: ProtoMember(5)] string PeakMemoryUsage,
    [property: ProtoMember(6)] int ActiveThreads
)
{
    public GetServerProcessInfoResponse() : this(default, default, default, default, default, default) { }
}

这允许您使用基本的位置记录声明,但允许您添加 ProtoBuf <3 想要的无参数构造函数。