protobuf-net:序列化一个空列表

我们在序列化一个空列表时遇到了一些问题。 这里有一些使用CF 2.0的.NET代码

//Generating the protobuf-msg ProtoBufMessage msg = new ProtoBufMessage(); msg.list = new List(); // 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超出范围),我们得到了一个exception。 但是具有空List的类型不应该是0字节,右(type-in​​formation?)?

我们需要这种类型的发送,因为在响应中是来自服务器的消息给我们的客户端。

有线格式(由谷歌定义 – 不在我的控制范围内!)只发送项目数据。 它不区分列表和列表。 因此,如果没有数据要发送 – 是的,长度为0(这是一种非常节俭的格式;-p)。

协议缓冲区不包括线路上的任何类型元数据。

另一个常见的问题是你可能会认为你的list属性被自动实例化为空,但它不会(除非你的代码执行它,可能在字段初始化器或构造函数中)。

这是一个可行的黑客:

 [ProtoContract] class SomeType { [ProtoMember(1)] public List Items {get;set;} [DefaultValue(false), ProtoMember(2)] private bool IsEmptyList { get { return Items != null && Items.Count == 0; } set { if(value) {Items = new List();}} } } 

哈基可能,但它应该工作。 如果你愿意,你也可以丢失Items “设置”,只需删除bool

  [ProtoMember(1)] public List Items {get {return items;}} private readonly List items = new List(); [DefaultValue(false), ProtoMember(2)] private bool IsEmptyList { get { return items.Count == 0; } set { } } 

正如@Marc所说,有线格式只发送项目数据,因此为了知道列表是空还是空,您必须将该位信息添加到流中。
添加额外属性以指示原始集合是否为空很容易但如果您不想修改原始类型定义,则还有另外两个选项:

使用代理进行序列化

代理类型将具有额外属性(保持原始类型不变)并将恢复列表的原始状态:null,包含items或为空。

  [TestMethod] public void SerializeEmptyCollectionUsingSurrogate_RemainEmpty() { var instance = new SomeType { Items = new List() }; // 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 Items { get; set; } } [ProtoContract] public class SomeTypeSurrogate { [ProtoMember(1)] public List 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() : value.Items } : null; } } 

使您的类型可扩展

protobuf-net建议使用IExtensible接口,它允许您扩展类型,以便可以将字段添加到消息中而不会破坏任何内容(请在此处阅读更多内容)。 为了使用protobuf-net扩展,您可以inheritanceExtensible类或实现IExtensible接口以避免inheritance约束。
现在您的类型是“可扩展的”,您可以定义[OnSerializing][OnDeserialized]方法,以便在重建具有原始状态的对象时添加将序列化到流并从中反序列化的新指示符。
优点是您不需要定义新属性或新类型作为代理,缺点是如果您的类型在类型模型中定义了子类型,则不支持IExtensible

  [TestMethod] public void SerializeEmptyCollectionInExtensibleType_RemainEmpty() { var instance = new Store { Products = new List() }; // 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 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(this, 101); if (productsListIsEmpty) this.Products = new List(); } } 
 public List BccAddresses { get; set; } 

你可以替换为:

 private List _BccAddresses; public List BccAddresses { get { return _BccAddresses; } set { _BccAddresses = (value != null && value.length) ? value : null; } }