如何更改我以前保存的列表类型以序列化为数组类型
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
之前,我们将一个属性序列化为一个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