protobuf-net支持C# 9的位置记录类型吗?

3

我试图在C#中使用protobuf-net和位置记录类型,但是遇到了以下异常:

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#类,则代码可以正常工作。然而,我希望记录也可以工作,因为它们避免了大量的空值警告。例如,System.Text.Json支持反序列化记录,这必须针对相同的限制进行工作。
我在文档、问题或StackOverflow上找不到任何东西,所以也许我擅长搜索,或者答案还没有出现。:-) protobuf-net存储库似乎也没有任何单元测试尝试序列化/反序列化C#记录,它只包含"RecordTypeTests",这似乎是检查记录是否可以克隆?

好的,如果我理解正确,那么问题就变成了:“protobuf-net是否支持没有默认构造函数的类?” - Moeri
1
@TheGeneral,不过还是有些微妙之处...等着回答吧。 - Marc Gravell
1
有趣的是,我的下一个语句是“插入Marc Gravell” :) - TheGeneral
@PeterB,那应该也解决了,是的。 - Marc Gravell
1
@TheGeneral 正确的语法应该是 StackExchange.StackOverflow.ValuedAssociates.MarcGravell.Summon(); - Lasse V. Karlsen
显示剩余5条评论
2个回答

10

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

使用您现有的代码,最简单的选项是告诉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代码版本中,它还可以在纯位置记录的情况下推断您要做什么,也就是说,以下内容也应该可以工作,并且基本上意思相同(作为一个细节:在这种情况下,它实际上会使用构造函数,而不是通过巫术创建一个实例,然后践踏值):

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

1
@Moeri "如果你想知道哪匹马跑得更快:比赛你的马"(我个人更喜欢第二个版本,因为记录应该是简洁和去除仪式感的) - Marc Gravell
如果您可以切换到一个可以使用参数化构造函数的库,比如ProtoBuf,那么普通的位置记录声明也将是我的选择。 - Lasse V. Karlsen
1
@LasseV.Karlsen(已删除的评论)-这就是为什么你不能再靠近赛道了。 - Marc Gravell
@MarcGravell 谢谢。我还有一个问题,如果可以的话:不带属性的解决方案是否支持继承?(它也能推断出“ProtoInclude”吗?) - Moeri
@Moeri 很好的问题;老实说我还没有尝试过;我可能应该尝试一下,但是我预计答案是“今天不行”。 - Marc Gravell
显示剩余13条评论

3

记录将会起作用,正如评论和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 所需的无参数构造函数。


今天我需要更多的咖啡,不知道我在这之前写了多少次.Zeron,最后只能用default,而且每次都将positional写成了position - Lasse V. Karlsen
小注:这是“踩扁值”版本 - 我的意思是,如果可能的话,它会使用new TheType()创建对象,然后回退到几个其他选项(Activator,然后是巫术) - 然后调用obj.Foo = x;obj.Bar = y;等等 - 而不是完全参数化构造函数版本,那里只需执行new TheType(x, y, z) - Marc Gravell
啊,好的,所以“践踏值”并不是与绕过构造函数有关的,只是反射部分。那我会更新我的答案。 - Lasse V. Karlsen
2
然而,我喜欢记录支持这样的反射,即使是使用init属性,这仍然可以使反序列化库(如Json.net和ProtoBuf)与这些类型无缝工作。 - Lasse V. Karlsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接