2

So there's no confusion, when I talk through my issue I am doing so as someone who is using the compiled classes that result from Bond schemas (that is to say I use "class" instead of "struct", etc.). I feel like it makes more cognitive sense to think of it this way.

I am using Microsoft Bond and I have a main class that has several properties, one of which is an instance of a derived class.

When creating an instance of the main class I have no problem setting the property to an instance of the derived class; however when I deserialize from binary back into the main class the property is now seen as its base class.

I have tried to cast it as the derived class but that throws a runtime exception.

The examples for using derived classes in the Bond documentation/manual have you specifying the derived class at the time of deserialization, but I am not deserializing just the derived class but the main class.

Here's an example of how I have the bond schema set up

struct BaseExample
{
   0: int property1;
}

struct DerivedExample : BaseExample
{
   0: int property2;
}

struct MainExample
{
   0: BaseExample mainProperty;
}

In usage I am setting mainProperty to an instance of the DerivedExample class. What I'd expect is that after deserialization, mainProperty is still of type DerivedExample (containing property2) but what I am seeing instead is mainProperty is of type BaseExample (and doesn't contain property2)

Am I forced to use generics to do this or is there something I am missing?

EDIT: Adding examples

My code that uses the classes generated from the Bond schemas is like this.

We have a calling service that creates a message of this type and uses Bond to serialize it into a byte array before sending it on a stream.

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);

Now we have a receiving service that is going to take this message off the stream and use Bond to deserialize it back into an object.

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
}

Full disclosure: I am actually using F# but I figured it would be better to do these in C#

4

1 回答 1

3

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

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

根据您正在建模的数据,我看到了两种设计模式的方法:

  1. 仿制药
  2. bonded

泛型

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

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

这实质上使您可以轻松地创建一堆具有相似形状的不同结构。但是这些结构不会有“是”的关系。像HandleMessage可能这样的方法也必须是通用的,从而导致通用级联。

保税

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

在 .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字段进行多态性时,我们需要有一些方法来知道要反序列化到哪个派生最多的类型。有时这可以从负载外部的上下文中得知(例如,可能处理的每个消息只有一种类型)。其他时候,我们需要将此信息嵌入到有效负载的公共部分。一种常见的方法是使用枚举:

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),可以在这种情况下使用。

支持 an 的默认数组OutputBuffer是 65K,因此几乎可以肯定会发送大量额外数据。

于 2017-02-06T22:21:57.223 回答