当集合是另一种类型的属性时,Protobuf-net将空集合反序列化为null

Rya*_*yer 7 protobuf-net

我遇到了protobuf-net的问题,我希望它是用户错误而不是protobuf-net的错误.

我可以序列化一个空集合(例如IDictionary<string, string>()),然后反序列化集合.反序列化导致非空对象(与我序列化完全一样).

但是,如果集合属于另一种类型,事情会变得很糟糕.使用非null空集合序列化自定义类型会导致集合为null的反序列化.

下面是我正在研究的内容和用于说明问题的单元测试的示例.

测试Test_Protobuf_EmptyDictionary_SerializeDeserialize通过.测试Test_Protobuf_EmptyCollectionAndPopulatedCollection_SerializeDeserialize失败.

[ProtoContract]
[ProtoInclude(100, typeof(ConcreteA))]
public abstract class AbstractClass
{
    [ProtoMember(1)]
    public string Name { get; set; }

    [ProtoMember(2)]
    public IDictionary<string, string> FieldsA { get; set; }

    [ProtoMember(3)]
    public IDictionary<string, string> FieldsB { get; set; }

    [ProtoMember(4)]
    public ICollection<string> FieldsC { get; set; }

    [ProtoMember(5)]
    public ICollection<string> FieldsD { get; set; } 
}

[ProtoContract]
[ProtoInclude(110, typeof(ConcreteB))]
public class ConcreteA : AbstractClass
{
    public ConcreteA() {}
}

[ProtoContract]
[ProtoInclude(120, typeof(ConcreteC))]
public class ConcreteB : ConcreteA
{
    [ProtoMember(1)]
    public int Age { get; set; }

    public ConcreteB() {}
}

[ProtoContract]
public class ConcreteC : ConcreteB
{
    [ProtoMember(1)]
    public string HairColor { get; set; }
}

[TestFixture]
public class ProtobufTests
{
    [Test]
    public void Test_Protobuf_EmptyDictionary_SerializeDeserialize()
    {
        IDictionary<string,string> dictionary = new Dictionary<string, string>();
        ICollection<string> collection = new List<string>();

        Assert.IsNotNull(dictionary);
        Assert.IsNotNull(collection);
        Assert.AreEqual(0, dictionary.Keys.Count);
        Assert.AreEqual(0, collection.Count);

        using (MemoryStream ms = new MemoryStream())
        {
            Serializer.Serialize(ms, dictionary);
            ms.Position = 0;
            var deserialized = Serializer.Deserialize<IDictionary<string, string>>(ms);

            Assert.IsNotNull(deserialized);
            Assert.AreEqual(0, deserialized.Keys.Count);
        }

        using (MemoryStream ms = new MemoryStream())
        {
            Serializer.Serialize(ms, collection);
            ms.Position = 0;
            var deserialized = Serializer.Deserialize<ICollection<string>>(ms);

            Assert.IsNotNull(deserialized);
            Assert.AreEqual(0, deserialized.Count);
        }
    }

    [Test]
    public void Test_Protobuf_EmptyCollectionAndPopulatedCollection_SerializeDeserialize()
    {
        ConcreteC c = new ConcreteC {
                                        FieldsA = new Dictionary<string, string>(),
                                        FieldsB = new Dictionary<string, string> {{"john", "elway"}},
                                        FieldsC = new List<string>(),
                                        FieldsD = new List<string>{"james", "jones"}
                                    };
        Assert.IsNotNull(c);
        Assert.IsNotNull(c.FieldsA);
        Assert.IsNotNull(c.FieldsB);
        Assert.IsNotNull(c.FieldsC);
        Assert.IsNotNull(c.FieldsD);
        Assert.AreEqual(0, c.FieldsA.Keys.Count);
        Assert.AreEqual(1, c.FieldsB.Keys.Count);
        Assert.AreEqual(0, c.FieldsC.Count);
        Assert.AreEqual(2, c.FieldsD.Count);

        using (MemoryStream ms = new MemoryStream())
        {
            Serializer.Serialize(ms, c);
            ms.Position = 0;
            var deserialized = Serializer.Deserialize<ConcreteC>(ms);

            Assert.IsNotNull(deserialized);
            Assert.IsNotNull(deserialized.FieldsA); // first failing test
            Assert.IsNotNull(deserialized.FieldsB);
            Assert.IsNotNull(deserialized.FieldsC);
            Assert.IsNotNull(deserialized.FieldsD);
            Assert.AreEqual(0, deserialized.FieldsA.Keys.Count);
            Assert.AreEqual(1, deserialized.FieldsB.Keys.Count);
            Assert.AreEqual(0, deserialized.FieldsC.Count);
            Assert.AreEqual(1, deserialized.FieldsD.Count);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 11

归根结底,这归结为protobuf规范没有概念null,也没有表达方式null.集合实际上只是一个repeated块(来自.proto规范); 和repeated0项块是两手空空 ; 没有字节; 没有.由于集合成员本身没有出现在protobuf中,因此无处可说无论是null还是null.

在xml术语中,if是否像使用[XmlElement]嵌入父项中的子项(而不是[XmlArray]/ [XmlArrayItem]- 即

<Foo>
    <Name>abc</Name>
    <Item>x</Item>
    <Item>y</Item>
    <Item>z</Item>
</Foo>
Run Code Online (Sandbox Code Playgroud)

a Foo,0 Items将是:

<Foo>
    <Name>abc</Name>
</Foo>
Run Code Online (Sandbox Code Playgroud)

从中,不可能说集合本身是否null.显然protobuf实际上不是xml,但上面仅仅是一个说明性的例子.

所以:protobuf无法表达这种情况,因此protobuf-net也没有.

在另一个场景中,您表示:空列表(作为根元素)的序列化表示形式为:零字节.反序列化时,如果它发现流是空的,它总是返回一个非空值作为根对象,这是您想要的最可能的版本.

  • 有没有办法改变这种行为?作为 Protobuf 的一个重要用例的在线类型往往是不可变的,并且通常避免使用 Null。我认为永远不应该使用 null,而应该使用空列表作为默认值;至少对于基于 IEnumerable 类型的集合字段,对于用户来说,集合的空列表将是更常见的情况。是否有我们可以应用到该字段的属性或某些东西来逐个字段地区分这种情况? (3认同)
  • @akara目前不在; 但是,你可以在构造函数中初始化属性 - 或者在C#"current"中:`List <Foo> Foos {get;} = new List <Foo>();` (2认同)