protobuf-net: сериализация пустого списка

у нас есть некоторые проблемы с сериализацией пустого списка. здесь некоторый код в .NET с использованием CF 2.0

//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 вне допустимого диапазона). Но тип с пустым списком не должен быть 0 байт, верно (информация о типе?)?

Такой тип отправки нам нужен, потому что в Response находятся сообщения от Сервера для нашего клиента.


person bopa    schedule 04.03.2010    source источник


Ответы (3)


Формат проводки (определенный Google, но не в моей компетенции!) отправляет данные только для элементов. Он не делает различий между пустым списком и пустым списком. Так что если нет данных для отправки - да, длина равна 0 (это очень экономный формат ;-p).

Буферы протоколов не включают в себя какие-либо метаданные типа в сети.

Еще одна распространенная ошибка здесь заключается в том, что вы можете предположить, что ваше свойство списка автоматически создается как пустое, но это не будет (если только ваш код не сделает это, возможно, в инициализаторе поля или конструкторе).

Вот рабочий хак:

[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 { }
    }
person Marc Gravell    schedule 04.03.2010
comment
Хотя Google определяет сериализацию таким образом, это означает, что это логично (это может быть для некоторых случаев, а не для других). Если мы используем protobuf для сохранения иерархии объектов, люди будут ожидать получить копию своей иерархии без какой-либо разницы, если они сериализуют и десериализуют). Я думаю, было бы неплохо добавить возможность принудительной сериализации пустой коллекции, чтобы воссоздать ту же иерархию объектов при десериализации. Я думаю, что количество голосов по вопросу должно частично оправдать мою просьбу ;-) - person Eric Ouellet; 20.10.2020
comment
Еще одна функция, которая была бы более универсальной, — это возможность пометить метод как [OnDeserialized] и вызвать его после завершения сериализации этого объекта. - person Eric Ouellet; 20.10.2020
comment
Мои 2 предыдущих комментария основаны на моем понимании того, что сериализация действительно обрабатывает коллекцию, являющуюся нулевой или пустой, таким же образом (ничего не сериализуя). - person Eric Ouellet; 20.10.2020
comment
@EricOuellet Я думаю, было бы неплохо добавить возможность принудительной сериализации пустой коллекции, чтобы воссоздать ту же иерархию объектов при десериализации - это интересная цель, но буквально нет способа выражая это в протоколе данных; сама коллекция не отображается в потоке - только содержимое, поэтому, если содержимого нет... - person Marc Gravell; 21.10.2020
comment
Спасибо Марк за ответ. Я нахожу это очень грустным. Согласно моим ожиданиям и, вероятно, большинству людей, именно сериализатор отвечает за сохранение точного состояния иерархии объектов. Всем, кто использует Protobuf и имеет одну или несколько пустых коллекций, придется писать дополнительный код, которого не должно быть в полнофункциональном сериализаторе. Мне очень нравится ваш сериализатор (производительность потрясающая), но это поведение промаха действительно лишает меня большого энтузиазма использовать его. - person Eric Ouellet; 21.10.2020

Как сказал @Marc, проводной формат отправляет данные только для элементов, поэтому, чтобы узнать, был ли список пустым или нулевым, вы должны добавить этот бит информации в поток.
Добавление дополнительного свойства, чтобы указать, был ли исходный коллекция была пустой или нет, это просто, но если вы не хотите изменять исходное определение типа, у вас есть еще два варианта:

Сериализация с использованием суррогата

Суррогатный тип будет иметь дополнительное свойство (сохраняя исходный тип нетронутым) и восстановит исходное состояние списка: нуль, с элементами или пустой.

    [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>();
        }
    }
person Tamir    schedule 09.07.2015

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; }
}
person Vlad Mysla    schedule 12.06.2015