Как правильно использовать производные классы в качестве полей объекта Microsoft 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 для сериализации его в байтовый массив перед отправкой в ​​потоке.

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#

1 ответ

Решение

Поведение среза, которое вы наблюдаете при десериализации, ожидается со схемами, как написано. MainExample.mainProperty поле имеет тип BaseExampleпоэтому, когда он сериализован, только BaseExample поля написаны. Не имеет значения, какой тип среды выполнения используется. Кроме того, когда он десериализован, только BaseExample поля будут реализованы.

Когда речь идет о наследовании и полиморфизме, Bond не включает никакой информации о типах в сериализованные полезные нагрузки: он оставляет решение о том, как смоделировать это, до дизайнера схемы. Это связано с философией Бонда о том, чтобы платить только за то, что вы используете.

В зависимости от данных, которые вы моделируете, я вижу два способа разработки вашей схемы:

  1. дженерики
  2. bonded

Дженерики

Как уже упоминалось в вопросе, MainExample Структура может быть сделана общей:

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

По сути, это позволяет легко создавать кучу различных структур с одинаковыми формами. Но эти структуры не будут иметь отношения "есть". Методы как HandleMessage Скорее всего, также должен быть общим, вызывая общий каскад.

облигационный

Чтобы включить поле в другую структуру, которая является полиморфной по типу, сделайте поле bonded поле Связанные поля не срезаются при сериализации. Кроме того, они не сразу десериализуются, поэтому принимающая сторона имеет возможность выбрать подходящий тип для десериализации.

В файле.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;
}

В примере C# polymorphic_container в репозитории Bond есть полностью проработанный пример такой отправки.

OutputBuffer.Data.Array

Я заметил, что в коде для отправки сообщения есть следующая строка, содержащая ошибку:

SendMessage(output.Data.Array);

OutputBuffer.Data собственность ArraySegment<byte>, который используется для представления фрагмента некоторого другого массива. Этот срез может быть короче всего массива (Count свойство), и оно может начинаться со смещения, отличного от 0 (Offset имущество). Большинство библиотек ввода / вывода имеют перегрузку SendMessage(byte[] buf, int offset, int count) это можно использовать в подобных случаях.

Массив по умолчанию, поддерживающий OutputBuffer 65K, так что почти наверняка отправлена ​​куча дополнительных данных.

Другие вопросы по тегам