c# - 意外的 protobuf-net 序列化程序行为

标签 c# serialization protobuf-net

我们正在使用 protobuf-net v.2.3.2 序列化和反序列化我们项目中的一些复杂对象(内部包含列表、字典等)。大多数时候,一切都很好,但在极少数情况下,我们会遇到非常奇怪的行为:如果调用序列化程序的 .FromProto<SomeComplexType>(bytes),在一个进程中序列化的对象会导致另一个进程中的反序列化错误。第二个过程中的方法之前没有调用 .ToProto(someComplexObject) .

这是一个示例:假设我们的流程 1 如下所示:

class Program1 {
    public static void Main()
    {
        SomeComplexType complexObject = new SomeComplexType();

        // Here goes some code filling complexObject with data

        byte[] serialized = ToProto(complexObject);

        File.WriteAllBytes("serialized.data", serialized);
    }

    public static byte[] ToProto(object value)
    {
        using (var stream = new MemoryStream())
        {
            ProtoBuf.Serializer.Serialize(stream, value);
            return stream.ToArray();
        }
    }

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

现在,我们正在尝试读取进程 2 中的那个对象:

class Program2 {
    public static void Main()
    {
        byte[] serialized = File.ReadAllBytes("serialized.data");

        SomeComplexType complexObject =                
            FromProto<SomeComplexType>(serialized);
    }

    public static byte[] ToProto(object value)
    {
        using (var stream = new MemoryStream())
        {
            ProtoBuf.Serializer.Serialize(stream, value);
            return stream.ToArray();
        }
    }

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

我们看到的是,在极少数情况下,进程 1 生成的文件使进程 2 在调用 FromProto 时失败(我们观察到各种错误,从“缺少无参数构造函数”到 StackOverflowException)。

但是,添加这样一行:ToProto(new SomeComplexType());在调用 FromProto 之前的某个地方使错误消失,并且相同的字节集被反序列化而没有任何障碍。似乎没有其他方法(我们尝试过 PrepareSerializer、GetSchema)可以解决问题。

看起来 ToProto 和 FromProto 解析对象模型的方式有一些细微的差别。另一点是,ProtoBuf 似乎“记住”了调用 ToProto 后的状态,这有助于它进行后续的反序列化。

更新: 这是更多详细信息: 我们的类结构看起来与此类似(非常简化):

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
[ProtoInclude(1, typeof(A))]
[ProtoInclude(2, typeof(B))]
public interface IBase
{
    [ProtoIgnore]
    string Id { get; }
}

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)]
public class A : IBase
{
    [ProtoIgnore]
    public string Id { get; }

    public string PropertyA { get; set; }
}

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)]
public class B : IBase
{
    [ProtoIgnore]
    public string Id { get; }

    public string PropertyB { get; set; }
}

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)]
public class C
{
    public List<IBase> ListOfBase = new List<IBase>();
}

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)]
public class D
{
    public C PropertyC { get; set; }
    public Dictionary<string, B> DictionaryOfBs { get; set; }
}

问题的根本原因似乎是 Protobuf-net 为类型准备序列化器的方式有些不确定。这是我们观察到的。

假设我们有两个程序:生产者和消费者。生产者创建 D 的实例,添加一些数据并使用 protobuf-net 序列化该实例。消费者获取序列化数据并将其反序列化为 D 的实例。

在生产者中,protobuf 有时会在发现 IBase 之前发现类型 B,因此它会为 B 生成序列化程序并将 DictionaryOfBs 中的值序列化为 B 的直接实例。

在消费者中,protobuf-net 可能会首先发现 IBase,因此当它为 B 准备(反)序列化器时,它会将其视为 IBase 的子类。因此,当谈到反序列化 DictionaryOfBs 的值时,它试图将它们作为 IBase 的子类读取,期望字段编号能够区分 A 和 B。流中的数据可能使得 IBase 序列化程序决定它看到的是一个实例A 的,尝试将其转换为 B(使用 Merge 方法)并进入无限递归尝试将 A 转换为 B 为 A 为 B 等,从而导致最终的 StackOverflowException。

在反序列化之前添加 Serializer.Serialize(stream, new D()) 会改变序列化器的创建顺序,所以在那种情况下没有错误,尽管这似乎是一个幸运的巧合.不幸的是,在我们的例子中,即使这样也不能用作令人满意的解决方法,因为这会导致偶尔出现“内部错误;发生 key 不匹配”反序列化错误。

最佳答案

序列化代码使用的是通用 API 但使用的是 <object>由于泛型类型推断。这会使事情变得困惑。首先要尝试的是 ToProto使用方法Serializer.NonGeneric.Serialize - 这将使用 .GetType()等等,希望能减少混淆。

或者:制作ToProtoT value 通用.

注意:我还没有对此进行测试 - 但这是尝试的第一件事。

关于c# - 意外的 protobuf-net 序列化程序行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47819458/

相关文章:

django - 如何编写自定义序列化器?

c# - 序列化 XNA 4.0

c# - 限制 protobuf-net 继承 "tree"

.net - Protobuf-net : ProtoInclude and compatibility 中的继承

c# - Dotnet 构建不适用于 newcsproj 和 PackageReference

c# - 即使应用程序处于后台,Time.unscaledDeltaTime 仍然在计数

javascript - 可以序列化node.js vm.script吗?

c# - protobuf.net 继承 - 使用 ImplicitFields = ImplicitFields.AllFields

c# - 如何让 C# 类在其构造函数中实例化另一个类?

c# - 将日期字符串转换为日期格式