protocol-buffers - 在 protobuf3 中发送显式零
问题描述
在 Protobuf3 中,零是数字类型的默认值,因此在序列化时它们会被过滤掉。
我有一个应用程序,只有在值发生更改时才需要发送它。例如,x 为 1,现在 x 为 0,发送此值。
不可能只发送增量,例如 -1,因为其中一些值是浮点数或双精度值,我们不希望产生错误。
在我需要序列化的某些类中有超过 200 个不同的变量,因此诸如“添加布尔值以标记哪些字段已更改”之类的解决方案是可能的,但并不有趣。其他具有大量每个字段工作或处理的建议也是不可取的。
是否有一种简单的机制来告诉 protobuf3 显式保留一个值,即使它是默认值?
可能的解决方案:
- 每次发送整个班级。这里的主要缺点是某些字段可能有很多数据。
- 在模式中使用布尔值“已更改”来指示变量是否已更改,即使它是 0
- 使用魔法值。可怕的想法,但可能。不打算这样做。
解决方案
由于您标记了 protobuf-net,因此您可以在字段级别执行此操作:
[ProtoMember(..., IsRequired = true)]
// your field
或全局(这里我假设您使用的是默认模型,这通常是一个非常安全的假设):
RuntimeTypeModel.Default.ImplicitZeroDefault = false;
你就完成了;
注意:如果您对增量感兴趣,您也可以有条件地执行此操作- 对于属性Foo
,您可以添加:
private bool ShouldSerializeFoo() { /* your rules here */ }
(这是许多序列化程序和其他工具使用的基于名称的模式;在某些情况下,它需要是public
,但 protobuf-net 通常对其非公开感到满意)
作为在内部跟踪增量状态的对象的一个重要示例:
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
static class P
{
static void Main()
{
var obj = new MyType
{
Foo = 42,
Bar = "abc",
Blap = DateTime.Now
};
ShowPayloadSize("original", obj);
obj.MarkClean();
ShowPayloadSize("clean", obj);
obj.Foo = 42;
obj.Bar = "abc";
ShowPayloadSize("set property to same", obj);
obj.Foo = 45;
obj.Bar = "new value";
ShowPayloadSize("set property to different", obj);
obj.MarkClean();
ShowPayloadSize("clean again", obj);
}
static void ShowPayloadSize<T>(string caption, T obj)
{
using var ms = new MemoryStream();
Serializer.Serialize(ms, obj);
Console.WriteLine($"{caption}: {ms.Length} bytes");
}
}
[ProtoContract]
public class MyType
{
private int _dirty = -1; // treat everything as dirty by default
public void MarkClean() => _dirty = 0;
public bool IsDirty => _dirty != 0;
private bool ShouldSerialize(int flag) => (_dirty & flag) != 0;
private void Set<T>(ref T field, T value, int flag)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
_dirty |= flag;
}
}
[ProtoMember(1)]
public int Foo
{
get => _foo;
set => Set(ref _foo, value, 1 << 0);
}
public bool ShouldSerializeFoo() => ShouldSerialize(1 << 0);
private int _foo;
[ProtoMember(2)]
public string Bar
{
get => _bar;
set => Set(ref _bar, value, 1 << 1);
}
public bool ShouldSerializeBar() => ShouldSerialize(1 << 1);
private string _bar;
[ProtoMember(3)]
public DateTime Blap
{
get => _blap;
set => Set(ref _blap, value, 1 << 2);
}
public bool ShouldSerializeBlap() => ShouldSerialize(1 << 2);
private DateTime _blap;
}