使用字典的 Protobuf-net 对象引用反序列化:引用跟踪对象在反序列化期间更改了引用

2024-01-14

我在尝试使用 protobuf-net 序列化/反序列化复杂对象图时遇到一些问题。

我正在开发一个遗留应用程序,我们正在使用 .Net Remoting 将 GUI 客户端连接到 C# 服务。由于使用默认值的对象图的序列化大小,我们发现海外用户的性能较差BinaryFormatter,客户端和服务器之间的有限带宽(1Mbit/s)加剧了这种情况。

作为一个快速的胜利,我想我应该整理一个概念证明,看看通过使用 protobuf-net 来代替,通过实现是否可以带来任何性能提升ISerializable。当我测试时,我遇到了一个问题,即未维护对象引用。

我整理了一个重现该问题的示例。我期待中的对象Dictionary(Items[1]) 和对象 B.A 将与我指定的相同AsReference=true in the ProtoMember属性。

Using protobuf-net 2.0.0.619,我看到反序列化时抛出异常(引用跟踪对象在反序列化期间更改了引用)。

如果这不是受支持的方案,请告诉我。

Test

[Test]
public void AreObjectReferencesSameAfterDeserialization()
{
    A a = new A();
    B b = new B();

    b.A = a;

    b.Items.Add(1, a);

    Assert.AreSame(a, b.A);
    Assert.AreSame(b.A, b.Items[1]);

    B deserializedB;

    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, b);
        stream.Seek(0, SeekOrigin.Begin);
        deserializedB = Serializer.Deserialize<B>(stream);
    }

    Assert.AreSame(deserializedB.A, deserializedB.Items[1]);
}

类定义

[Serializable]
[ProtoContract]
public class A
{
}

[Serializable]
[ProtoContract]
public class B
{
    [ProtoMember(1, AsReference = true)]
    public A A { get; set; }

    [ProtoMember(2, AsReference = true)]
    public Dictionary<int, A> Items { get; set; }

    public B()
    {
        Items = new Dictionary<int, A>();
    }
}

编辑:这应该从下一个版本开始工作,只需标记类型的AsReferenceDefault:

[ProtoContract(AsReferenceDefault=true)]
public class A
{
    // ...
}

目前这是sort of一个不受支持的场景 - 至少,通过属性它不受支持;基本上,AsReference=true 现在指的是KeyValuePair<int,A>,这实际上没有意义,因为KeyValuePair<int,A>是一个值类型(因此永远不能将其视为参考;我在本地副本中为此添加了更好的消息)。

Because KeyValuePair<int,A>(默认情况下)充当tuple,目前没有地方支持AsReference信息,但我想更好地支持这种情况,我将对此进行调查。

also一个错误意味着AsReference元组(甚至引用类型元组)上的问题变得无序,但我已经在本地修复了这个问题;这就是“已更改”消息的来源。

理论上,我做这件事的工作量并不大;这基础知识已经可以工作了,奇怪的是,昨晚它也在推特上单独出现——我想“字典指向一个对象”是一个非常常见的场景。据猜测,我想我会添加一些属性来帮助描述这种情况,但实际上您现在可以使用几种不同的路线来解决它:

1:配置KeyValuePair<int,A>手动:

[Test]
public void ExecuteHackedViaFields()
{
    // I'm using separate models **only** to keep them clean between tests;
    // normally you would use RuntimeTypeModel.Default
    var model = TypeModel.Create();

    // configure using the fields of KeyValuePair<int,A>
    var type = model.Add(typeof(KeyValuePair<int, A>), false);
    type.Add(1, "key");
    type.AddField(2, "value").AsReference = true;

     // or just remove AsReference on Items
    model[typeof(B)][2].AsReference = false;

    Execute(model);
}

我不太喜欢这个,因为它利用了实现细节KeyValuePair<,>(私有字段),并且可能无法在 .NET 版本之间工作。我更愿意replace KeyValuePair<,>即时通过代理人:

[Test]
public void ExecuteHackedViaSurrogate()
{
    // I'm using separate models **only** to keep them clean between tests;
    // normally you would use RuntimeTypeModel.Default
    var model = TypeModel.Create();

    // or just remove AsReference on Items
    model[typeof(B)][2].AsReference = false;

    // this is the evil bit: configure a surrogate for KeyValuePair<int,A>
    model[typeof(KeyValuePair<int, A>)].SetSurrogate(typeof(RefPair<int, A>));
    Execute(model);
}

[ProtoContract]
public struct RefPair<TKey,TValue> {
    [ProtoMember(1)]
    public TKey Key {get; private set;}
    [ProtoMember(2, AsReference = true)]
    public TValue Value {get; private set;}
    public RefPair(TKey key, TValue value) : this() {
        Key = key;
        Value = value;
    }
    public static implicit operator KeyValuePair<TKey,TValue>
        (RefPair<TKey,TValue> val)
    {
        return new KeyValuePair<TKey,TValue>(val.Key, val.Value);
    }
    public static implicit operator RefPair<TKey,TValue>
        (KeyValuePair<TKey,TValue> val)
    {
        return new RefPair<TKey,TValue>(val.Key, val.Value);
    }
}

这配置了要使用的东西代替 KeyValuePair<int,A>(通过运算符转换)。

在这两者中,Execute只是:

private void Execute(TypeModel model)
{
    A a = new A();
    B b = new B();

    b.A = a;

    b.Items.Add(1, a);

    Assert.AreSame(a, b.A);
    Assert.AreSame(b.A, b.Items[1]);

    B deserializedB = (B)model.DeepClone(b);

    Assert.AreSame(deserializedB.A, deserializedB.Items[1]);
}

不过,我确实想添加直接支持。上述两者的好处是,当我有时间这样做时,您只需删除自定义配置代码即可。

为了完整起见,如果您的代码使用Serializer.*方法,然后而不是创建/配置new模型,您应该配置default model:

RuntimeTypeModel.Default.Add(...); // etc

Serializer.*基本上是一个捷径RuntimeTypeModel.Default.*.

最后:你不应该创建一个新的TypeModel每次通话;这会损害性能。您应该创建并配置一个模型实例,并多次重复使用它。或者只使用默认模型。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用字典的 Protobuf-net 对象引用反序列化:引用跟踪对象在反序列化期间更改了引用 的相关文章

  • GetType() 在 Type 实例上返回什么?

    我在一些调试过程中遇到了这段代码 private bool HasBaseType Type type out Type baseType Type originalType type GetType baseType GetBaseTyp
  • 序列化对象以进行单元测试

    假设在单元测试中我需要一个对象 其中所有 50 个字段都设置了一些值 我不想手动设置所有这些字段 因为这需要时间而且很烦人 不知何故 我需要获得一个实例 其中所有字段都由一些非空值初始化 我有一个想法 如果我要调试一些代码 在某个时候我会得
  • 如何判断计算机是否已重新启动?

    我曾经使用过一个命令行 SMTP 邮件程序 作为试用版的限制 它允许您在每个 Windows 会话中最多接收 10 封电子邮件 如果您重新启动计算机 您可能还会收到 10 个以上 我认为这种共享软件破坏非常巧妙 我想在我的应用程序中复制它
  • 从同一个类中的另一个构造函数调用构造函数

    我有一个带有两个构造函数的类 C 这是代码片段 public class FooBar public FooBar string s constructor 1 some functionality public FooBar int i
  • 查看 NuGet 包依赖关系层次结构

    有没有一种方法 文本或图形 来查看 NuGet 包之间的依赖关系层次结构 如果您使用的是新的 csproj 您可以在此处获取所有依赖项 在项目构建后 项目目录 obj project assets json
  • 对 std::vector 进行排序但忽略某个数字

    我有一个std vector
  • 告诉 Nancy 将枚举序列化为字符串

    Nancy 默认情况下在生成 JSON 响应时将枚举序列化为整数 我需要将枚举序列化为字符串 有一种方法可以通过创建来自定义 Nancy 的 JSON 序列化JavaScript 原始转换器 https github com NancyFx
  • 将 Long 转换为 DateTime 从 C# 日期到 Java 日期

    我一直尝试用Java读取二进制文件 而二进制文件是用C 编写的 其中一些数据包含日期时间数据 当 DateTime 数据写入文件 以二进制形式 时 它使用DateTime ToBinary on C 为了读取 DateTime 数据 它将首
  • 为什么可以通过ref参数修改readonly字段?

    考虑 class Foo private readonly string value public Foo Bar ref value private void Bar ref string value value hello world
  • 如何在 C 中安全地声明 16 位字符串文字?

    我知道已经有一个标准方法 前缀为L wchar t test literal L Test 问题是wchar t不保证是16位 但是对于我的项目 我需要16位wchar t 我还想避免通过的要求 fshort wchar 那么 C 不是 C
  • 为什么这个二维指针表示法有效,而另一个则无效[重复]

    这个问题在这里已经有答案了 这里我编写了一段代码来打印 3x3 矩阵的对角线值之和 这里我必须将矩阵传递给函数 矩阵被传递给指针数组 代码可以工作 但问题是我必须编写参数的方式如下 int mat 3 以下导致程序崩溃 int mat 3
  • 检测到严重错误 c0000374 - C++ dll 将已分配内存的指针返回到 C#

    我有一个 c dll 它为我的主 c 应用程序提供一些功能 在这里 我尝试读取一个文件 将其加载到内存 然后返回一些信息 例如加载数据的指针和内存块的计数到 c Dll 成功将文件读取到内存 但在返回主应用程序时 程序由于堆损坏而崩溃 检测
  • 在屏幕上获取字符

    我浏览了 NCurses 函数列表 似乎找不到返回已打印在屏幕上的字符的函数 每个字符单元格中存储的字符是否有可访问的值 如果没有的话Windows终端有类似的功能吗 我想用它来替换屏幕上某个值的所有字符 例如 所有a s 具有不同的特征
  • WebBrowser.Print() 等待完成。 。网

    我在 VB NET 中使用 WebBrowser 控件并调用 Print 方法 我正在使用 PDF 打印机进行打印 当调用 Print 时 它不会立即启动 它会等到完成整个子或块的运行代码 我需要确保我正在打印的文件也完整并继续处理该文件
  • 将数组作为参数传递

    如果我们修改作为方法内参数传递的数组的内容 则修改是在参数的副本而不是原始参数上完成的 因此结果不可见 当我们调用具有引用类型参数的方法时 会发生什么过程 这是我想问的代码示例 using System namespace Value Re
  • 可访问性不一致:参数类型的可访问性低于方法

    我试图在两个表单之间传递一个对象 基本上是对当前登录用户的引用 目前 我在登录表单中有一些类似的内容 private ACTInterface oActInterface public void button1 Click object s
  • 堆栈是向上增长还是向下增长?

    我在 C 中有这段代码 int q 10 int s 5 int a 3 printf Address of a d n int a printf Address of a 1 d n int a 1 printf Address of a
  • 使用 omp_set_num_threads() 将线程数设置为 2,但 omp_get_num_threads() 返回 1

    我有以下使用 OpenMP 的 C C 代码 int nProcessors omp get max threads if argv 4 NULL printf argv 4 s n argv 4 nProcessors atoi argv
  • 使用 C 在 OS X 中获取其他进程的 argv

    我想获得其他进程的argv 例如ps 我使用的是在 Intel 或 PowerPC 上运行的 Mac OS X 10 4 11 首先 我阅读了 ps 和 man kvm 的代码 然后编写了一些 C 代码 include
  • 如何使用 C++11 using 语法键入定义函数指针?

    我想写这个 typedef void FunctionPtr using using 我该怎么做呢 它具有类似的语法 只不过您从指针中删除了标识符 using FunctionPtr void 这是一个Example http ideone

随机推荐