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

How do I change my previously saved List type to serialize into an Array type

之前,我们将一个属性序列化为一个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 
        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; }
}

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

这似乎是 byte[] 属性的问题。如果我将 属性 类型更改为 int []List<int>,则该行为不可重现。问题源于这样一个事实,即有两种方法可以在 Protocol Buffer 中对数组进行编码:作为重复的 key/value 对或 "packed" 作为具有长度分隔值块的单个键。

对于字节数组,protobuf-net 使用一种特殊的序列化程序,BlobSerializer,它只是简单地写入字节数组长度,然后将内容块复制到输出缓冲区中作为打包的重复字段。它在读取时进行反向操作——不处理数据实际为重复 key/value 格式的情况。

另一方面,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. Thus when a byte with its high bit set is serialized, it is encoded as two bytes. On the other hand a byte [] member is serialized like a string as a length-delimited sequence of raw bytes 的长度分隔序列。因此,字节数组中的一个字节在编码中总是被编码为字节——这与 List<byte> 的编码不兼容。

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

[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() as shown for instance in this answer.

ArrayHolder 替换为 ListHolder