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

时间:2010-03-04 11:00:41

标签: c# .net serialization protobuf-net

我们在序列化空列表时遇到一些问题。 这里有一些使用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字节,右(type-in​​formation?)?

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

3 个答案:

答案 0 :(得分:32)

有线格式(由谷歌定义 - 不在我的控制范围内!)仅发送项目的数据。它不区分列表和 null 列表。因此,如果没有数据要发送 - 是的,长度为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>();}}
    }
}

Hacky也许,但它应该有用。如果您愿意,也可以丢失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 { }
    }

答案 1 :(得分:1)

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

使用Surrogate进行序列化

代理类型将具有额外属性(保持原始类型不变)并将恢复列表的原始状态: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接口,它允许您扩展类型,以便可以将字段添加到消息中而不会中断任何内容(阅读更多here)。为了使用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>();
        }
    }

答案 2 :(得分: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; }
}