如何更改以前保存的List类型以序列化为数组类型

时间:2015-10-30 05:48:49

标签: c# serialization protobuf-net

以前,我们将属性序列化为List<byte> 现在我们要将其更改为byte[]。 理解您应该能够在版本之间自由交换集合类型,但我们得到ProtoBuf.ProtoException

[TestFixture, Category("Framework")]
class CollectionTypeChange 
{
    [Test]
    public void TestRoundTrip()
    {
        var bytes = new List<byte>() {1,2,4};
        var a = new ArrayHolder(bytes);

        var aCopy = Deserialize<ArrayHolder>(Serialize(a));

        //Passes
        Assert.That(aCopy.CollectionOfBytes, Is.EquivalentTo(a.CollectionOfBytes));
    }

    [Test]
    public void TestChangeArrayToList()
    {
        var bytes = new List<byte>() { 1, 2, 4 };
        var a = new ArrayHolder(bytes);

        var aCopy = Deserialize<ListHolder>(Serialize(a));

        //Passes
        Assert.That(aCopy.CollectionOfBytes, Is.EquivalentTo(a.CollectionOfBytes));
    }

    [Test]
    public void TestChangeListToArray()
    {
        var bytes = new List<byte>() { 1, 2, 4 };
        var a = new ListHolder(bytes);

        //Throws: ProtoBuf.ProtoException : Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see http://stackoverflow.com/q/2152978/23354
        var aCopy = Deserialize<ArrayHolder>(Serialize(a));

        Assert.That(aCopy.CollectionOfBytes, Is.EquivalentTo(a.CollectionOfBytes));
    }

    public static byte[] Serialize<T>(T obj)
    {
        using (var stream = new MemoryStream())
        {
            Serializer.Serialize(stream, obj);
            return stream.ToArray();
        }
    }

    public static T Deserialize<T>(byte[] buffer)
    {
        using (var stream = new MemoryStream(buffer))
        {
            return Serializer.Deserialize<T>(stream);
        }
    }
}

[ProtoContract]
internal class ArrayHolder
{
    private ArrayHolder()
    {
        CollectionOfBytes = new byte[0] {};
    }

    internal ArrayHolder(IEnumerable<byte> bytesToUse )
    {
        CollectionOfBytes = bytesToUse.ToArray();
    }

    [ProtoMember(1)]
    public byte[] CollectionOfBytes { get; set; }
}

[ProtoContract]
internal class ListHolder
{
    private ListHolder()
    {
        CollectionOfBytes = new List<byte>();
    }

    internal ListHolder(IEnumerable<byte> bytesToUse)
    {
        CollectionOfBytes = bytesToUse.ToList();
    }

    [ProtoMember(1)]
    public List<byte> CollectionOfBytes { get; set; }
}

关于数组或字节是否有特殊之处,这意味着它不像我们预期的那样工作?

1 个答案:

答案 0 :(得分:1)

这看起来是一个特别针对byte[]属性的问题。如果我将属性类型更改为int []List<int>,则行为不可重现。问题产生于以下事实:在协议缓冲区中有两种方法对数组进行编码:作为重复的键/值对,或"packed"作为具有长度分隔的值块的单个键。

对于字节数组,protobuf-net使用一个特殊的序列化器BlobSerializer,它只是写入字节数组长度,然后将内容块作为打包重复字段复制到输出缓冲区中。它在读取时执行相反操作 - 当数据实际上处于重复键/值格式时不处理。

另一方面,List<byte>使用通用ListDecorator序列化。它的Read()方法测试以查看当前在输入缓冲区中的格式并正确读取它 - 打包或解包。但是,它的Write()方法会将字节数组默认解压缩。随后,当将缓冲区读入byte []数组时,BlobSerializer会抛出异常,因为格式不符合预期。可以说这是protobuf-net BlobSerializer的一个错误。

但是,有一个简单的解决方法:通过设置List<byte>来声明IsPacked = true应该以打包格式序列化:

[ProtoContract]
internal class ListHolder
{
    private ListHolder()
    {
        CollectionOfBytes = new List<byte>();
    }

    internal ListHolder(IEnumerable<byte> bytesToUse)
    {
        CollectionOfBytes = bytesToUse.ToList();
    }

    [ProtoMember(1, IsPacked = true)]
    public List<byte> CollectionOfBytes { get; set; }
}

这对于你的字节列表也应该是一个更紧凑的表示。

不幸的是,当字节集合包含设置了高位的字节时,上述解决方法失败。 Protobuf-net将打包的List<byte>序列化为长度分隔的Base 128 Varints序列。因此,当高位设置的字节被串行化时,它被编码为两个字节。另一方面,byte []成员像字符串一样序列化为length-delimited sequence of raw bytes。因此,字节数组中的一个字节始终在编码中编码为字节 - 这与List<byte>的编码不兼容。

作为解决方法,可以使用List<byte>类型中的私有代理ArrayHolder属性:

[ProtoContract]
internal class ArrayHolder
{
    private ArrayHolder()
    {
        CollectionOfBytes = new byte[0] { };
    }

    internal ArrayHolder(IEnumerable<byte> bytesToUse)
    {
        CollectionOfBytes = bytesToUse.ToArray();
    }

    [ProtoIgnore]
    public byte[] CollectionOfBytes { get; set; }

    [ProtoMember(1, OverwriteList = true)]
    List<byte> ListOfBytes
    {
        get
        {
            if (CollectionOfBytes == null)
                return null;
            return new List<byte>(CollectionOfBytes);
        }
        set
        {
            if (value == null)
                return;
            CollectionOfBytes = value.ToArray();
        }
    }
}

示例fiddle

或者,可以使用MetaType.SetSurrogate()在(反)序列化期间用ArrayHolder替换ListHolder,如this answer中所示。