32

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

//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 超出范围)。但是一个空 List 的类型不应该是 0 字节,对吧(类型信息?)?

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

4

3 回答 3

38

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

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

这里的另一个常见问题是,您可能会假设您的 list 属性会自动实例化为空,但事实并非如此(除非您的代码这样做,可能在字段初始化程序或构造函数中)。

这是一个可行的技巧:

[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“设置”,然后删除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 { }
    }
于 2010-03-04T13:01:06.607 回答
3

正如@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>();
        }
    }
于 2015-07-09T07:50:24.907 回答
0
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; }
}
于 2015-06-12T13:20:27.047 回答