【问题标题】:protobuf-net: Serializing an empty Listprotobuf-net:序列化一个空列表
【发布时间】:2011-01-23 15:13:24
【问题描述】:

我们在序列化空列表时遇到了一些问题。 这里有一些使用 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 字节,对吧(类型信息?)?

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

【问题讨论】:

    标签: c# .net serialization protobuf-net


    【解决方案1】:

    正如@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 接口,该接口允许您扩展类型,以便可以将字段添加到消息中而不会破坏任何内容(阅读更多 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】:
      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; }
      }
      

      【讨论】:

        【解决方案3】:

        有线格式(由 google 定义 - 不在我的控制范围内!)仅发送 items 的数据。它不区分 empty 列表和 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 { }
            }
        

        【讨论】:

        • 虽然谷歌以这种方式定义序列化确实意味着它是合乎逻辑的(它可能适用于某些情况,而不适用于其他情况)。如果我们使用 protobuf 来持久化对象层次结构,人们会期望在序列化和反序列化时获得其层次结构的副本而没有任何区别)。我认为添加一个选项来强制空集合的序列化以便在反序列化时重新创建相同的对象层次结构将是一个很棒的功能。我认为对该问题的赞成票数应该部分证明我的要求是合理的;-)
        • 另一个更通用的功能是可以将方法标记为“[OnDeserialized]”并在该对象上完成序列化后调用。
        • 我之前的 2 个 cmets 是基于我的理解,即序列化确实会以相同的方式处理一个为 null 或为空的集合(不序列化)。
        • @EricOuellet “我认为添加一个选项来强制空集合的序列化以便在反序列化时重新创建相同的对象层次结构是一个很棒的功能” - 这是一个有趣的目标,但是在数据协议中实际上没有办法表达;集合本身不会出现在流中 - 只是内容,所以如果内容为零......
        • 感谢马克的回答。我觉得这真的很难过。根据我的期望和可能大多数人的期望,序列化程序有责任保持对象层次结构的确切状态。使用 Protobuf 并拥有一个或多个空集合的每个人都必须编写额外的代码,而这些代码不应该存在于功能齐全的序列化程序中。我真的很喜欢你的序列化器(性能很棒),但“错过行为”真的让我失去了很多使用它的热情。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-01-14
        • 1970-01-01
        • 1970-01-01
        • 2012-04-26
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多