列表的 Protobuf.net 对象图序列化

Posted

技术标签:

【中文标题】列表的 Protobuf.net 对象图序列化【英文标题】:Protobuf.net object graph serialization for lists 【发布时间】:2011-09-08 12:02:29 【问题描述】:

我了解使用 protobuf.net 的列表不支持 AsReference,因此我尝试了解决此限制的方法。我创建了一个名为 SuperList 的自定义列表,其中包含包装在 SuperListItem 类型的对象中的项目,如下所示:

[ProtoContract]
public class SuperList<T> where T : class

    [ProtoMember(1)]
    private List<SuperListItem<T>> _items = new List<SuperListItem<T>>();

    public SuperList()
    
    

    public int IndexOf(T item)
    
        int indexOf = -1;
        for (int index = 0; index < _items.Count; index++)
        
            if (_items[index].Item == item)
            
                indexOf = index;
                break;
            
        
        return indexOf;
    

    public void Insert(int index, T item)
    
        _items.Insert(index, new SuperListItem<T>(item));
    

    public void RemoveAt(int index)
    
        _items.RemoveAt(index);
    

    public T this[int index]
    
        get
        
            return _items[index].Item;
        
        set
        
            _items[index] = new SuperListItem<T>(value);
        
    

    public void Add(T item)
    
        _items.Add(new SuperListItem<T>(item));
    

    public void Clear()
    
        _items.Clear();
    

    public bool Contains(T item)
    
        bool contains = false;
        foreach (var listItem in _items)
        
            if (listItem.Item == item)
            
                contains = true;
                break;
            
        
        return contains;
    

    public void CopyTo(T[] array, int arrayIndex)
    
        for (int index = arrayIndex; index < _items.Count; index++)
            array[index] = _items[index].Item;
    

    public int Count
    
        get  return _items.Count; 
    

    public bool IsReadOnly
    
        get  return false; 
    

    public bool Remove(T item)
    
        SuperListItem<T> itemToRemove = null;
        foreach (var listItem in _items)
        
            if (listItem.Item == item)
            
                itemToRemove = listItem;
                break;
            
        
        if (itemToRemove != null)
            _items.Remove(itemToRemove);

        return itemToRemove != null;
    

    public IEnumerator<T> GetEnumerator()
    
        foreach(var listItem in _items)
            yield return listItem.Item;
    


[ProtoContract]
public class SuperListItem<T>

    [ProtoMember(1, AsReference = true)]
    private readonly T _item;
    public T Item  get  return _item;  

    private SuperListItem()  

    public SuperListItem(T item)
    
        _item = item;
    

我有如下的序列化测试代码:

[ProtoContract]
public class Thing

    [ProtoMember(1)]
    private readonly string _name;
    public string Name  get  return _name;  

    private Thing()  

    public Thing(string name)
    
        _name = name;
    


public class ProtoTest3

    public void Serialize()
    
        SuperList<Thing> list = GetListOfThings();

        using (var fs = File.Create(@"c:\temp\things.bin"))
        
            ProtoBuf.Serializer.Serialize(fs, list);

            fs.Close();
        

        using (var fs = File.OpenRead(@"c:\temp\things.bin"))
        
            list = ProtoBuf.Serializer.Deserialize<SuperList<Thing>>(fs);

            Debug.Assert(list[0] == list[2]);

            fs.Close();
        
    

    private SuperList<Thing> GetListOfThings()
    
        var thing1 = new Thing("thing1");
        var thing2 = new Thing("thing2");

        var list = new SuperList<Thing>();
        list.Add(thing1);
        list.Add(thing2);
        list.Add(thing1);

        return list;
    

但是,当我运行代码时,我得到异常“没有为此对象定义无参数构造函数”。它只是 ProtoBuf.Net 中的一个限制,还是我做错了什么。有没有办法解决这个问题?

【问题讨论】:

【参考方案1】:

澄清;列表的限制仅仅是 列表本身 不被视为参考。 项目 ,只要它们位于标记为AsReference 的成员上 - 例如:

    [Test]
    public void SerializeTheEasyWay()
    
        var list = GetListOfThings();

        using (var fs = File.Create(@"things.bin"))
        
            ProtoBuf.Serializer.Serialize(fs, list);

            fs.Close();
        

        using (var fs = File.OpenRead(@"things.bin"))
        
            list = ProtoBuf.Serializer.Deserialize<MyDto>(fs);

            Assert.AreEqual(3, list.Things.Count);
            Assert.AreNotSame(list.Things[0], list.Things[1]);
            Assert.AreSame(list.Things[0], list.Things[2]);

            fs.Close();
        
    

    [ProtoContract]
    public class MyDto
    
        [ProtoMember(1, AsReference = true)]
        public List<Thing> Things  get; set; 
    

    private MyDto GetListOfThings()
    
        var thing1 = new Thing("thing1");
        var thing2 = new Thing("thing2");

        var list = new List<Thing>();
        list.Add(thing1);
        list.Add(thing2);
        list.Add(thing1);

        return new MyDto Things = list;
    

(有趣的事实 - 这实际上是 一模一样 在电线上)

确实,但是,在这种情况下,它创建实例的方式似乎存在错误;它正在使用.ctor 并且失败了。我会调查并解决这个问题,但以下方法也可以:

1:将无参数.ctor公开:

        public Thing()
        
        

2:或者,禁用.ctor

    [ProtoContract(SkipConstructor = true)]
    public class Thing
     ...

我将调查为什么私有无参数构造函数在这种情况下不满意。

【讨论】:

感谢 Marc,在“Thing”类上公开构造函数解决了我原来的问题。我曾假设构造函数错误只是 AsReference 不可用于列表的副产品(因为如果我删除了 AsReference,它与私有构造函数一起工作)。 @EasyTimer 是的,在这种情况下,它与对象创建有关。

以上是关于列表的 Protobuf.net 对象图序列化的主要内容,如果未能解决你的问题,请参考以下文章

Protobuf .NET 后反序列化处理程序

如何使用 Protobuf.net C# 序列化自定义对象基础属性

带有 ProtoBuf.net 的通用序列化器

Protobuf.net 异常 - 检查元数据时超时

Protobuf.net 内存使用情况

protobuf.net 和条件序列化