首页 > 解决方案 > 在 protobuf3 中发送显式零

问题描述

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

我有一个应用程序,只有在值发生更改时才需要发送它。例如,x 为 1,现在 x 为 0,发送此值。

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

在我需要序列化的某些类中有超过 200 个不同的变量,因此诸如“添加布尔值以标记哪些字段已更改”之类的解决方案是可能的,但并不有趣。其他具有大量每个字段工作或处理的建议也是不可取的。

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

可能的解决方案:

标签: protocol-buffersprotobuf-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;
}

推荐阅读