如何正确使用派生 类 作为 Microsoft Bond 对象的字段

How do I correctly use derived classes as fields of a Microsoft Bond object

所以没有混淆,当我讨论我的问题时,我是作为使用编译的 classes 的人这样做的,这些结果来自 Bond 模式(也就是说我使用 "class"而不是 "struct",等等)。我觉得这样想更有意义。

我正在使用 Microsoft Bond,我有一个主 class,它有几个属性,其中之一是派生 class 的一个实例。

创建主 class 的实例时,我可以将 属性 设置为派生 class 的实例;然而,当我从二进制反序列化回主 class 时,属性 现在被视为它的基础 class.

我试图将其转换为派生的 class,但这会引发运行时异常。

在 Bond documentation/manual 中使用派生 classes 的示例是否在反序列化时指定了派生 class,但我不只是反序列化派生 class 但主要 class.

这是我如何设置绑定模式的示例

struct BaseExample
{
   0: int property1;
}

struct DerivedExample : BaseExample
{
   0: int property2;
}

struct MainExample
{
   0: BaseExample mainProperty;
}

在使用中,我将 mainProperty 设置为 DerivedExample class 的一个实例。 我期望的是,在反序列化之后,mainProperty 仍然是 DerivedExample 类型(包含 属性2),但我看到的是 mainProperty 是 BaseExample 类型(并且不包含 属性2)

我是被迫使用泛型来做这件事还是我遗漏了什么?

编辑:添加示例

我使用从 Bond 模式生成的 classes 的代码是这样的。

我们有一个调用服务可以创建这种类型的消息,并使用 Bond 将其序列化为字节数组,然后再通过流发送。

var message = new MainExample();

var derivedExample = new DerivedExample()
{
    property1 = 1,
    property2 = 2,        
};
message.mainProperty = derivedExample;

// This block is all from the Bond examples
var output = new OutputBuffer();
var writer = new CompactBinaryWriter<OutputBuffer>(output);
Serialize.To(writer, message);

SendMessage(output.Data.Array);

现在我们有一个接收服务,它将把这个消息从流中取出并使用 Bond 将它反序列化回一个对象。

void HandleMessage(byte[] messageBA)
{
    // This block is all from the Bond examples
    var input = new InputBuffer(messageBA);
    var reader = new CompactBinaryReader<InputBuffer>(input);
    MainExample message = Deserialize<BondEvent>.From(reader);

    // mainProperty is now of type BaseExample and not DerivedExample
    message.mainProperty.property1; // is accessable
    message.mainProperty.property2; // will not compile

    DerivedExample castedProperty = message.mainProperty as DerivedExample; // fails at runtime
}

完全披露:我实际上使用的是 F#,但我认为在 C# 中执行这些操作会更好

您在反序列化时观察到的切片行为与编写的模式一致。 MainExample.mainProperty字段是BaseExample类型的,所以在序列化时,只写入BaseExample字段。使用哪种运行时类型并不重要。另外,反序列化时,只有BaseExample个字段会被实现。

在处理继承和多态性时,Bond 不在序列化有效载荷中包含任何类型信息:它将如何建模的决定留给架构设计者。这源于邦德的只为使用付费的理念。

根据您建模的数据,我看到了两种设计架构的方法:

  1. 仿制药
  2. bonded

泛型

如问题中所述,MainExample 结构可以通用:

struct MainExample<T>
{
    0: T mainProperty;
}

这实质上允许您轻松创建一堆具有相似形状的不同结构。但是这些结构不会有 "is a" 关系。 HandleMessage 之类的方法可能也必须是通用的,从而导致通用级联。

保税

要在另一个类型多态的结构中包含一个字段,请将该字段设置为 bonded field。绑定字段在序列化时不切片。此外,它们不会立即反序列化,因此接收方有机会选择合适的类型进行反序列化。

在 .bond 文件中,我们有这个:

struct MainExample
{
   0: bonded<BaseExample> mainProperty;
}

要序列化,如下:

var message = new MainExample();

var derivedExample = new DerivedExample()
{
    property1 = 1,
    property2 = 2,        
};
message.mainProperty = new Bonded<DerivedExample>(derivedExample);
// NB: new Bonded<BaseExample>(derivedExample) WILL slice

反序列化:

void HandleMessage(byte[] messageBA)
{
    // This block is all from the Bond examples
    var input = new InputBuffer(messageBA);
    var reader = new CompactBinaryReader<InputBuffer>(input);
    MainExample message = Deserialize<BondEvent>.From(reader);

    DerivedExample de = message.mainProperty.Deserialize<DerivedExample>();
}

当使用 bonded 字段进行多态性时,我们需要有一些方法来知道要反序列化到哪个 most-derived 类型。有时这是从有效载荷外部的上下文中获知的(例如,可能处理的每个消息只有一种类型)。其他时候,我们需要将此信息嵌入有效负载的公共部分。一种常见的方法是使用枚举:

enum PropertyKind
{
    Base;
    Derived;
}

struct MainExample
{
    0: bonded<BaseExample> mainProperty;
    1: PropertyKind mainPropertyKind = Base;
}

在 Bond 存储库的 C# polymorphic_container 示例中有一个执行这种调度的完整示例。

OutputBuffer.Data.Array

我注意到在发送消息的代码中,有以下一行,其中包含一个错误:

SendMessage(output.Data.Array);

OutputBuffer.Data 属性 是一个 ArraySegment<byte> ,用于表示其他数组的切片。此切片可能比整个数组(Count 属性)短,并且它可能从 0 以外的偏移量开始(Offset 属性)。大多数 I/O 库都有像 SendMessage(byte[] buf, int offset, int count) 这样的重载,可以在这种情况下使用。

支持 OutputBuffer 的默认数组是 65K,因此几乎可以肯定发送了一堆额外的数据。