Сериализация графа объектов Protobuf.net для списков

Я понимаю, что AsReference не поддерживается для списков с protobuf.net, поэтому я попытался обойти это ограничение. Я создал пользовательский список с именем 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, или я что-то не так сделал. Есть ли способ обойти эту проблему?


person Simon Williams    schedule 08.09.2011    source источник


Ответы (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
    { ...

Я исследую, почему частные конструкторы без параметров не работают в этом сценарии.

person Marc Gravell    schedule 08.09.2011
comment
Спасибо, Марк, создание общедоступного конструктора в классе Thing решило мою первоначальную проблему. Я предположил, что ошибка конструктора была просто побочным продуктом того, что AsReference не был доступен для списков (поскольку он работал с частным конструктором, если я удалил AsReference). - person Simon Williams; 08.09.2011
comment
@EasyTimer да, в этом случае это как-то связано с созданием объекта. - person Marc Gravell; 08.09.2011