Protobuf-net 序列化/反序列化
本文关键字:反序列化 序列化 Protobuf-net | 更新日期: 2023-09-27 18:31:17
我检查了,但似乎看不到如何将类直接序列化为字节数组,然后使用 Marc Gravell 的 protobuf-net 实现从字节数组反序列化。
编辑:我更改了问题并提供了代码,因为如何序列化为 byte[] 而不必通过流的原始问题无疑是微不足道的。我道歉。
更新的问题:有没有办法不必处理泛型,而是在通过构造函数传递属性"MessageBody"时通过反射推断属性的类型?我假设我无法序列化对象类型,对吗?当前的解决方案看起来非常麻烦,因为每次实例化新消息时,我都需要传入 MessageBody 的类型。有没有更时尚的解决方案?
我想出了以下内容:
class Program
{
static void Main(string[] args)
{
Message<string> msg = new Message<string>("Producer", "Consumer", "Test Message");
byte[] byteArray = msg.Serialize();
Message<string> message = Message<string>.Deserialize(byteArray);
Console.WriteLine("Output");
Console.WriteLine(message.From);
Console.WriteLine(message.To);
Console.WriteLine(message.MessageBody);
Console.ReadLine();
}
}
[ProtoContract]
public class Message<T>
{
[ProtoMember(1)]
public string From { get; private set; }
[ProtoMember(2)]
public string To { get; private set; }
[ProtoMember(3)]
public T MessageBody { get; private set; }
public Message()
{
}
public Message(string from, string to, T messageBody)
{
this.From = from;
this.To = to;
this.MessageBody = messageBody;
}
public byte[] Serialize()
{
byte[] msgOut;
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, this);
msgOut = stream.GetBuffer();
}
return msgOut;
}
public static Message<T> Deserialize(byte[] message)
{
Message<T> msgOut;
using (var stream = new MemoryStream(message))
{
msgOut = Serializer.Deserialize<Message<T>>(stream);
}
return msgOut;
}
}
我喜欢得到的是这样的东西:
MessagenewMsg = new Message("Producer", "Consumer", Foo);byte[] byteArray = newMsg.Serialize();
和Message msg = Message.Deserialize(byteArray);
(其中反序列化是一个静态方法,它总是反序列化为消息类型的对象,并且只需要知道将消息正文反序列化为哪种类型)。
这里有几个不同的问题,所以我会回答我能看到的:如果我错过了什么,请告诉我。
首先,如前所述,MemoryStream 是获取 byte[] 的最常见方式。这与大多数序列化程序一致 - 例如,XmlSerializer,BinaryFormatter和DataContractSerializer也没有"as a byte[]重载",但会接受MemoryStream。
泛型:你不需要使用泛型;v1 有 Serializer.NonGeneric,它把这个从你那里包装出来。在 v2 中,"核心"是非泛型的,可以通过 RuntimeTypeModel.Default 访问;当然,Serializer 和 Serializer.NonGeneric 将继续工作。
对于必须包含类型的问题:是的,protobuf 规范假设接收者知道他们被赋予的数据类型。此处一个简单的选项是使用简单的包装器对象作为"root"对象,为数据提供多个类型化属性(其中只有一个是非 null)。另一个选项可能来自通过ProtoInclude的内置继承支持(注意:作为实现细节,这两种方法是相同的)。
在您的特定示例中,也许可以考虑:
[ProtoContract]
[ProtoInclude(1, typeof(Message<Foo>))]
.... More as needed
[ProtoInclude(8, typeof(Message<Bar>))]
public abstract class Message
{ }
[ProtoContract]
public class Message<T> : Message
{
...
}
然后只需使用 <Message>
进行序列化 - API 将自动创建正确的类型。
对于最近的版本,还有一个 DynamicType 选项,其中包含您的类型数据,例如:
[ProtoContract]
public class MyRoot {
[ProtoMember(1, DynamicType=true)]
public object Value { get; set; }
}
这将适用于任何包含协定类型实例的值(但不适用于原语,理想情况下不涉及继承)。
OP 发布的代码对我不太有用,以下是对 Marc Gravell 建议的轻微调整。需要从消息继承以防止"不允许循环继承",并且如下面的代码注释中所述,GetBuffer 也没有成功。
希望它能帮助别人,我花了几个小时才让它全部工作......
[ProtoContract]
public abstract class Message
{
public byte[] Serialize()
{
byte[] result;
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, this);
result = stream.ToArray(); //GetBuffer was giving me a Protobuf.ProtoException of "Invalid field in source data: 0" when deserializing
}
return result;
}
}
[ProtoContract]
public class Message : Message
{
[ProtoMember(1)]
public string From { get; private set; }
[ProtoMember(2)]
public string To { get; private set; }
[ProtoMember(3)]
public T MessageBody { get; private set; }
public Message()
{ }
public Message(string from, string to, T messageBody)
{
this.From = from;
this.To = to;
this.MessageBody = messageBody;
}
public static Message Deserialize(byte[] message)
{
Message result;
using (var stream = new MemoryStream(message))
{
result = Serializer.Deserialize>(stream);
}
return result;
}
}
我还发现,如果使用.PROTO 文件(.cs使用 Google.Protobuf 和 Google.Protobuf.Tools 包编译/生成的),您将使用该记录。序列化程序中的 WriteTo 函数和反序列化程序中的静态 Parser.ParseFrom 函数。
using Confluent.Kafka;
using System;
using System.IO;
using CodedOutputStream = Google.Protobuf.CodedOutputStream;
using SerializationContext = Confluent.Kafka.SerializationContext;
public class KafkaSerializer: ISerializer<Message>
{
public byte[] Serialize(Message record, SerializationContext context)
{
using (var stream = new MemoryStream())
{
using (var codedStream = new CodedOutputStream(stream))
{
record.WriteTo(codedStream);
codedStream.Flush();
return stream.ToArray();
}
}
}
}
public class KafkaDeserializer : IDeserializer<Message>
{
public Message Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
{
var msg = Message.Parser.ParseFrom(data);
return msg;
}
}