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

因此,当我讨论我的问题时,我会以使用由Bond模式产生的已编译类的人(也就是说,我使用“类”而不是“结构”等)进行混淆。我觉得这样思考更有意义。

我正在使用Microsoft Bond,并且我有一个具有几个属性的主类,其中一个是派生类的实例。

创建主类的实例时,将属性设置为派生类的实例没有问题。但是,当我从二进制反序列化回主类时,该属性现在被视为其基类。

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

在Bond文档/手册中使用派生类的示例让您在反序列化时指定派生类,但是我不只是对派生类进行反序列化,而是对主类进行反序列化。

这是我如何设置绑定架构的示例

struct BaseExample
{
   0: int property1;
}

struct DerivedExample : BaseExample
{
   0: int property2;
}

struct MainExample
{
   0: BaseExample mainProperty;
}


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

我是否被迫使用泛型来执行此操作,或者我缺少什么?

编辑:添加示例

我的代码使用的是从Bond模式生成的类,就像这样。

我们有一个调用服务,该服务创建此类型的消息,并使用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在序列化的有效负载中不包含任何类型信息:它将如何建模的决定权交给模式设计者。这源于邦德的哲学,即只为您使用的东西付费。

根据要建模的数据,我看到两种设计模式的方法:


仿制药
bonded


泛型

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

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


从本质上讲,这使您可以轻松创建一堆形状相似的不同结构。但是这些结构不会有“是”关系。像 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字段进行多态处理时,我们将需要某种方式来知道将哪个最大派生类型反序列化为该类型。有时,这是从有效负载外部的上下文中得知的(例如,也许处理的每个消息只有一种类型)。其他时候,我们需要将此信息嵌入有效负载的公共部分。常用的方法是使用枚举:

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,因此几乎肯定会发送大量额外数据。