如何在Protobuf中实现VARIANT

Pie*_*kel 5 .net c# protocol-buffers protobuf-net

作为 protobuf 协议的一部分,我需要能够发送动态类型的数据,有点像VARIANT。粗略地说,我要求数据是整数、字符串、布尔值或“其他”,其中“其他”(例如DateTime)被序列化为字符串。我需要能够将它们用作单个字段并在协议中多个不同位置的列表中使用。

如何才能最好地实现这一点,同时保持消息大小最小和性能最佳?

我正在使用 protobuf-net 和 C#。

编辑:
我在下面发布了一个建议的答案,其中使用了我认为所需的最小内存。

EDIT2:在http://github.com/pvginkel/ProtoVariant创建了一个具有完整实现的
github.com 项目。

Mar*_*ell 4

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
Run Code Online (Sandbox Code Playgroud)

bool ShouldSerialize*()如果您不喜欢空值,也可以使用该模式执行相同的操作。

将其包装在 a 中class,您应该可以在字段级别或列表级别使用它。您提到最佳性能;我可以建议的唯一额外的事情是也许考虑将其视为“组”而不是“子消息”,因为这更容易编码(并且只要您期望数据,就同样容易解码)。为此,请使用Grouped数据格式 via [ProtoMember],即

[ProtoMember(12, DataFormat = DataFormat.Group)]
public MyVariant Foo {get;set;}
Run Code Online (Sandbox Code Playgroud)

然而,这里的差异可能很小 - 但它避免了输出流中的一些回溯来修复长度。无论哪种方式,就开销而言,“子消息”至少需要 2 个字节;“至少一个”用于字段标头(如果12实际上是,则可能需要更多1234567) - “至少一个”用于长度,对于较长的消息,长度会变得更大。一个组占用 2 x 字段标头,因此如果您使用低字段编号,则无论封装数据的长度如何(可能是 5MB 的二进制),这都将是 2 个字节。

一个单独的技巧对于更复杂的场景很有用,但不具有互操作性,它是泛型继承,即一个抽象基类,其中ConcreteType<int>ConcreteType<string>列为子类型 - 然而,这需要额外的 2 个字节(通常),因此并不那么节俭。

距离核心规范又进一步,如果您确实无法判断需要支持哪些类型,并且不需要互操作性 - 有一些支持在数据中包含(优化的)类型信息;请参阅DynamicType选项ProtoMember- 这比其他两个选项占用更多空间。