Как правильно использовать производные классы в качестве полей объекта 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 не включает никакой информации о типах в сериализованные полезные нагрузки: он оставляет решение о том, как смоделировать это, до дизайнера схемы. Это связано с философией Бонда о том, чтобы платить только за то, что вы используете.
В зависимости от данных, которые вы моделируете, я вижу два способа разработки вашей схемы:
- дженерики
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, так что почти наверняка отправлена куча дополнительных данных.