在 protobuf3 中发送显式零

Sending explicit zeroes in protobuf3

在 Protobuf3 中,零是数字类型的默认值,因此在序列化时会过滤掉它们。

我有一个应用程序,我只需要在值发生变化时发送值。例如,x 是 1,现在 x 是 0,发送这个值。

不可能只发送增量值,例如 -1,因为其中一些值是浮点数或双精度值,我们不想产生错误。

在我需要序列化的某些 class 中有超过 200 个不同的变量,因此像 "add a boolean to flag which fields have changed" 这样的解决方案是可能的,但并不有趣。其他具有大量每个字段工作或处理的建议也是不可取的。

是否有一种简单的机制告诉 protobuf3 明确保留一个值,即使它是默认值?

可能的解决方案:

由于您标记了 ,您可以在字段级别执行此操作:

[ProtoMember(..., IsRequired = true)]
// your field

或全局(这里我假设您使用的是默认模型,这通常是一个非常安全的假设):

RuntimeTypeModel.Default.ImplicitZeroDefault = false;

大功告成;


注意:如果您对增量感兴趣,也可以有条件地执行此操作 - 对于 属性 Foo,您可以添加:

private bool ShouldSerializeFoo() { /* your rules here */ }

(这是许多序列化程序和其他工具使用的基于名称的模式;在某些情况下,它需要 public,但 protobuf-net 通常对此很满意 non-public)


作为在内部跟踪增量状态的对象的重要示例:

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;
}

如果你需要区分0null那么你可以使用proto3 wrapper类型:https://developers.google.com/protocol-buffers/docs/reference/csharp-generated#wrapper_types 这种情况有特殊的包装器类型:StringWrapper, Int32Wrapper and etc。与 C# 值类型(Int32Wrapper, DoubleWrapper, BoolWrapper 等)对应的所有包装器类型都映射到 Nullable<T>,其中 T 是相应的不可空类型。