作为我的 protobuf 协议(protocol)的一部分,我需要能够发送动态类型的数据,有点像 VARIANT .粗略地说,我要求数据是整数、字符串、 bool 值或“其他”,其中“其他”(例如 DateTime
)被序列化为字符串。我需要能够将它们用作单个字段,并在协议(protocol)中多个不同位置的列表中使用。
如何在保持最小消息大小和最佳性能的同时最好地实现这一点?
我在 C# 中使用 protobuf-net。
编辑:
我在下面发布了一个建议的答案,它使用了我认为所需的最小内存。
编辑 2:
在 http://github.com/pvginkel/ProtoVariant 创建了一个 github.com 项目具有完整的实现。
最佳答案
Jon 的多个选项涵盖了最简单的设置,尤其是在您需要跨平台支持的情况下。在 .NET 方面(确保您不会序列化不必要的值),只需从任何不匹配的属性返回 null
,例如:
public object Value { get;set;}
[ProtoMember(1)]
public int? ValueInt32 {
get { return (Value is int) ? (int)Value : (int?)null; }
set { Value = value; }
}
[ProtoMember(2)]
public string ValueString {
get { return (Value is string) ? (string)Value : null; }
set { Value = value; }
}
// etc
如果您不喜欢空值,您也可以使用 bool ShouldSerialize*()
模式执行相同的操作。
将其包装在 class
中,您应该可以在字段级别或列表级别使用它。您提到最佳性能;我唯一可以建议的是考虑将其视为“组”而不是“子消息”,因为这更容易编码(并且同样容易解码,只要您期望数据)。为此,请通过 Grouped
使用 [ProtoMember]
数据格式,即
[ProtoMember(12, DataFormat = DataFormat.Group)]
public MyVariant Foo {get;set;}
然而,这里的区别可能很小——但它避免了输出流中的一些回溯来固定长度。无论哪种方式,就开销而言,“子消息”至少需要 2 个字节; “至少一个”用于字段 header (如果 12
实际上是 1234567
则可能需要更多) - 并且“至少一个”用于长度,对于较长的消息会变得更大。一个组占用 2 倍的字段 header ,因此如果您使用低字段编号,则无论封装数据的长度如何(它可能是 5MB 的二进制文件),这都将是 2 个字节。
一个单独的技巧,对更复杂的场景有用但不是互操作的,是通用继承,即一个抽象基类,它有 ConcreteType<int>
, ConcreteType<string>
等列为子类型 - 然而,这需要额外的 2 个字节(通常),所以没那么节俭。
又远离核心规范,如果您真的不能告诉您需要支持哪些类型,并且不需要互操作性 - 有一些支持在数据中包含(优化的)类型信息;请参阅 DynamicType
上的 ProtoMember
选项 - 这比其他两个选项占用更多空间。
关于c# - 如何在 Protobuf 中实现 VARIANT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6519533/