Protobuf-net: сериализация пустого списка
У нас есть некоторые проблемы с сериализацией пустого списка.
здесь некоторый код в .NET с использованием CF 2.0
//Generating the protobuf-msg
ProtoBufMessage msg = new ProtoBufMessage();
msg.list = new List<AnotherProtobufMessage>();
// Serializing and sending throw HTTP-POST
MemoryStream stream = new MemoryStream();
Serializer.Serialize(stream, msg);
byte[] bytes = stream.ToArray();
HttpWebRequest request = createRequest();
request.ContentLength = bytes.Length ;
using (Stream httpStream = request.GetRequestStream())
{
httpStream.Write(bytes, 0, bytes.Length);
}
мы получили исключение, когда пытаемся написать в потоке (bytes.length вне диапазона).
Но тип с пустым списком не должен быть 0 байт, правый (тип-информация?)?
Нам нужен этот тип отправки, потому что в ответе есть сообщения от сервера для нашего клиента.
Ответы
Ответ 1
Формат проводов (определенный Google - не внутри моего контроля!) отправляет только данные для элементов. Он не делает различий между пустым списком и нулевым списком. Поэтому, если нет данных для отправки - да, длина равна 0 (это очень скромный формат; -p).
Буферы протокола не включают метаданные какого-либо типа в провод.
Еще одна распространенная проблема заключается в том, что вы можете предположить, что свойство вашего списка автоматически создается как пустое, но оно не будет (если только ваш код этого не делает, возможно, в инициализаторе поля или конструкторе).
Здесь работоспособный взлом:
[ProtoContract]
class SomeType {
[ProtoMember(1)]
public List<SomeOtherType> Items {get;set;}
[DefaultValue(false), ProtoMember(2)]
private bool IsEmptyList {
get { return Items != null && Items.Count == 0; }
set { if(value) {Items = new List<SomeOtherType>();}}
}
}
Возможно, Хакки, но он должен работать. Вы также можете потерять Items
"set", если хотите, и просто отпустите bool
:
[ProtoMember(1)]
public List<SomeOtherType> Items {get {return items;}}
private readonly List<SomeOtherType> items = new List<SomeOtherType>();
[DefaultValue(false), ProtoMember(2)]
private bool IsEmptyList {
get { return items.Count == 0; }
set { }
}
Ответ 2
Как сказал @Marc, формат проводов отправляет только данные для элементов, поэтому, чтобы знать, был ли список пустым или нулевым, вы должны добавить этот бит информации в поток.
Добавление дополнительного свойства для указания, было ли исходная коллекция пустым или нет, легко, но если вы не хотите изменять исходное определение типа, у вас есть еще два варианта:
Сериализация с использованием суррогата
Суррогатный тип будет иметь дополнительное свойство (сохраняя оригинальный тип нетронутым) и восстановит исходное состояние списка: null, с элементами или пустым.
[TestMethod]
public void SerializeEmptyCollectionUsingSurrogate_RemainEmpty()
{
var instance = new SomeType { Items = new List<int>() };
// set the surrogate
RuntimeTypeModel.Default.Add(typeof(SomeType), true).SetSurrogate(typeof(SomeTypeSurrogate));
// serialize-deserialize using cloning
var clone = Serializer.DeepClone(instance);
// clone is not null and empty
Assert.IsNotNull(clone.Items);
Assert.AreEqual(0, clone.Items.Count);
}
[ProtoContract]
public class SomeType
{
[ProtoMember(1)]
public List<int> Items { get; set; }
}
[ProtoContract]
public class SomeTypeSurrogate
{
[ProtoMember(1)]
public List<int> Items { get; set; }
[ProtoMember(2)]
public bool ItemsIsEmpty { get; set; }
public static implicit operator SomeTypeSurrogate(SomeType value)
{
return value != null
? new SomeTypeSurrogate { Items = value.Items, ItemsIsEmpty = value.Items != null && value.Items.Count == 0 }
: null;
}
public static implicit operator SomeType(SomeTypeSurrogate value)
{
return value != null
? new SomeType { Items = value.ItemsIsEmpty ? new List<int>() : value.Items }
: null;
}
}
Сделайте ваши типы расширяемыми
protobuf-net предлагает интерфейс IExtensible, который позволяет вам расширять типы, чтобы поля могли быть добавлены в сообщение без каких-либо взломов (подробнее здесь). Чтобы использовать расширение protobuf-net, вы можете наследовать класс Extensible
или реализовать интерфейс IExtensible
, чтобы избежать ограничения наследования.
Теперь, когда ваш тип "расширяемый", вы определяете методы [OnSerializing]
и [OnDeserialized]
для добавления новых индикаторов, которые будут сериализованы в поток и десериализованы из него при восстановлении объекта с его исходным состоянием.
Плюсы в том, что вам не нужно определять новые свойства или новые типы как суррогаты, минусы заключаются в том, что IExtensible
не поддерживается, если ваш тип имеет подтипы, определенные в вашей модели типа.
[TestMethod]
public void SerializeEmptyCollectionInExtensibleType_RemainEmpty()
{
var instance = new Store { Products = new List<string>() };
// serialize-deserialize using cloning
var clone = Serializer.DeepClone(instance);
// clone is not null and empty
Assert.IsNotNull(clone.Products);
Assert.AreEqual(0, clone.Products.Count);
}
[ProtoContract]
public class Store : Extensible
{
[ProtoMember(1)]
public List<string> Products { get; set; }
[OnSerializing]
public void OnDeserializing()
{
var productsListIsEmpty = this.Products != null && this.Products.Count == 0;
Extensible.AppendValue(this, 101, productsListIsEmpty);
}
[OnDeserialized]
public void OnDeserialized()
{
var productsListIsEmpty = Extensible.GetValue<bool>(this, 101);
if (productsListIsEmpty)
this.Products = new List<string>();
}
}
Ответ 3
public List<NotificationAddress> BccAddresses { get; set; }
вы можете заменить на:
private List<NotificationAddress> _BccAddresses;
public List<NotificationAddress> BccAddresses {
get { return _BccAddresses; }
set { _BccAddresses = (value != null && value.length) ? value : null; }
}