在 Proto-Buf 中使用 bool 类型

发布于 2024-09-07 16:17:11 字数 427 浏览 8 评论 0原文

我在我的应用程序中使用 protobuf-net 进行序列化/反序列化。我面临一个问题。

[ProtoContract()]
ClsTest
{
    private bool _isPeriodic

    [ProtoMember(1)]
    public bool IsPeriodic
    {
        get
        {
             return _isPeriodic;
        }

        set
        {
            isPeriodic = value;
        }
   }

}

我在我的集​​合对象中使用这个类。

序列化过程工作正常,但反序列化后,属性 IsPeriodic 的值默认为 true,尽管在某些情况下为 false。谁能帮助我吗?

I am using protobuf-net in my application for serializaion/deserialization. I am facing an issue.

[ProtoContract()]
ClsTest
{
    private bool _isPeriodic

    [ProtoMember(1)]
    public bool IsPeriodic
    {
        get
        {
             return _isPeriodic;
        }

        set
        {
            isPeriodic = value;
        }
   }

}

I am using this class in my collction object.

The serialization process workes fine, but after deserialization the Value for property IsPeriodic by default is true eventhough it was false for some cases. Can anyone help me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

我早已燃尽 2024-09-14 16:17:16

我遇到了类似的问题,默认为 true 的 bool 成员没有从配置文件中读取它们的值。

[ProtoMember(1, IsRequired=true), DefaultValue(true)]
public bool IsPeriodic
...

I had the a similar issue, where bool members that default to true were not reading their value from the config file.

[ProtoMember(1, IsRequired=true), DefaultValue(true)]
public bool IsPeriodic
...
只有一腔孤勇 2024-09-14 16:17:15

我发现使用 protobuf-net 处理 bool 和 enum 类型有一些技巧。
关于 bool 和 enum 类型默认值的第一个问题:
这是我的 Linq 片段代码:

[ProtoContract]
public class MyOption
{
    [ProtoMember(2)]
    public View m_printListView = View.Details;   (A)
    [ProtoMember(5) ]
    public bool m_bool = true ;                   (B)
}

void Main()
{
    string fname = @"D:/test.dat";
    if (File.Exists(fname) )
    {
        File.Delete(fname);
    }
    using(FileStream fs=  new FileStream(fname, FileMode.OpenOrCreate, FileAccess.Write) )
    {
        MyOption opt = new MyOption();
        opt.m_printListView = View.LargeIcon; // (1)
        opt.m_bool = false;                   // (2)

        Serializer.SerializeWithLengthPrefix(fs, opt, PrefixStyle.Fixed32);
    }
    using(FileStream fs=  new FileStream(@"D:/test.dat", FileMode.Open, FileAccess.Read) )
    {
        MyOption opt;
        opt = Serializer.DeserializeWithLengthPrefix<MyOption>(fs, PrefixStyle.Fixed32);
        Console.WriteLine(opt.m_printListView);
        Console.WriteLine(opt.m_bool);
    }
}

现在,猜测输出。它是:

Details
True

请注意,在 (A) 和 (B) 中,我将默认值设置为 View.Details 和 true。
在 (1) 和 (2) 中,在 proto-buf 序列化之后,我显式将值设置为 View.LargeIcon 和 false
反序列化,我们得到了错误的值。

原因是:对于bool值来说,默认值是false,根据proto-buf的设计原则,它会尽可能节省空间,所以默认值不会保存在文件中,只保存一个标志来指示应该使用默认值值(我猜,未验证)。

反序列化时,首先调用默认构造函数,而行(B)实际上是CLR运行时默认构造函数的一部分,然后proto-buf反序列化过程启动,发现成员m_bool有一个默认值标志,然后使用默认值 false 来设置 m_bool,这将覆盖 (B) 处的默认值。

对于enum类型的原因类似,在上面的例子中View.LargeIcon是默认值,其数值为0(通过Reflected验证)。

要使用 bool 和枚举成员的 DefaultValueAttribute 修复它:

[ProtoContract]
public class MyOption
{
    [ProtoMember(2), DefaultValue(View.Details)]
    public View m_printListView = View.Details;   (A)
    [ProtoMember(5), DefaultValue(true) ]
    public bool m_bool = true ;                   (B)
}

对于枚举类型,还有其他问题:
第一个是所有枚举器应该有不同的值,否则proto-buf在序列化时会抛出异常,例如System.Drawing.RotateFlip枚举类型具有以下定义:

    public enum RotateFlipType
{
    Rotate180FlipNone = 2,
    Rotate180FlipX = 6,
    Rotate180FlipXY = 0,
    Rotate180FlipY = 4,
    Rotate270FlipNone = 3,
    Rotate270FlipX = 7,
    Rotate270FlipXY = 1,
    Rotate270FlipY = 5,
    Rotate90FlipNone = 1,
    Rotate90FlipX = 5,
    Rotate90FlipXY = 3,
    Rotate90FlipY = 7,
    RotateNoneFlipNone = 0,
    RotateNoneFlipX = 4,
    RotateNoneFlipXY = 2,
    RotateNoneFlipY = 6
}

从图像处理的角度来看,RotateNoneFlipNone和Rotate180FlipXY具有相同的效果,所以他们有
相同的底层值,这是一个合理的设计,但是,这样的枚举不能与 proto-buf 一起使用:

    The enum System.Drawing.RotateFlipType has conflicting values RotateNoneFlipNone and RotateNoneFlipNone
Serializer.ThrowInner (Exception exception)
  at ProtoBuf.Serializer.ThrowInner(Exception exception)
  at ProtoBuf.Serializer.Serialize[T](Stream destination, T instance)
  at ProtoBuf.Serializer.SerializeWithLengthPrefix[T](Stream destination, T instance, PrefixStyle style, Int32 tag)

我的解决方法是创建自己的枚举并在 My_RotateFlipType 和 System.Drawing.RotateFlipType 之间使用一对一映射。只有 My_RotateFlipType 会被 proto-buf 序列化。

public enum RotateFlipType              public enum My_RotateFlipType
{                                       {
    Rotate180FlipNone = 2,                  Rotate180FlipNone,
    Rotate180FlipX = 6,                     Rotate180FlipX,
    Rotate180FlipXY = 0,                    Rotate180FlipXY,
    Rotate180FlipY = 4,                     Rotate180FlipY,
    Rotate270FlipNone = 3,                  Rotate270FlipNone,
    Rotate270FlipX = 7,                     Rotate270FlipX,
    Rotate270FlipXY = 1,                    Rotate270FlipXY,
    Rotate270FlipY = 5,                     Rotate270FlipY,
    Rotate90FlipNone = 1,                   Rotate90FlipNone,
    Rotate90FlipX = 5,                      Rotate90FlipX,
    Rotate90FlipXY = 3,                     Rotate90FlipXY,
    Rotate90FlipY = 7,                      Rotate90FlipY,
    RotateNoneFlipNone = 0,                 RotateNoneFlipNone,
    RotateNoneFlipX = 4,                    RotateNoneFlipX,
    RotateNoneFlipXY = 2,                   RotateNoneFlipXY,
    RotateNoneFlipY = 6                     RotateNoneFlipY
}                                       }

为了避免手动同步两个数据成员,我使用 ProtoBeforeSerialization 和 OnProtoAfterDeserialization 功能来自动化它:

[ProtoAfterDeserialization()]
public void OnProtoAfterDeserialization()
{
    Console.WriteLine("called OnProtoAfterDeserialization");
    bool ret = Enum.TryParse(m_rotate.ToString(), out m_rotate_protobuf);
}

[ProtoBeforeSerialization()]
public void OnProtoBeforeSerialization()
{
    Console.WriteLine("called OnProtoBeforeSerialization");
    bool ret = Enum.TryParse(m_rotate_protobuf.ToString(), out m_rotate);
}

关于枚举的第二个问题是 0 值枚举器。如果枚举没有值为 0 的枚举器,则
protobuf 在运行时很容易抛出异常。

使用 protobuf-net 时我将遵循以下规则:
1. 每当默认构造函数设置默认值以外的值时,请使用 DefaultValueAttribute。
2.对于系统或第三方枚举类型,在将其添加到protobuf之前,请使用reflector(静态)或linq(运行时)检查它是否存在上述问题。如果发生冲突,请使用上述解决方法。

I found working with bool and enum type with protobuf-net has some tricks.
The first issue about default value for both bool and enum type:
Here's my Linq snippet code:

[ProtoContract]
public class MyOption
{
    [ProtoMember(2)]
    public View m_printListView = View.Details;   (A)
    [ProtoMember(5) ]
    public bool m_bool = true ;                   (B)
}

void Main()
{
    string fname = @"D:/test.dat";
    if (File.Exists(fname) )
    {
        File.Delete(fname);
    }
    using(FileStream fs=  new FileStream(fname, FileMode.OpenOrCreate, FileAccess.Write) )
    {
        MyOption opt = new MyOption();
        opt.m_printListView = View.LargeIcon; // (1)
        opt.m_bool = false;                   // (2)

        Serializer.SerializeWithLengthPrefix(fs, opt, PrefixStyle.Fixed32);
    }
    using(FileStream fs=  new FileStream(@"D:/test.dat", FileMode.Open, FileAccess.Read) )
    {
        MyOption opt;
        opt = Serializer.DeserializeWithLengthPrefix<MyOption>(fs, PrefixStyle.Fixed32);
        Console.WriteLine(opt.m_printListView);
        Console.WriteLine(opt.m_bool);
    }
}

Now, guess the output. It's:

Details
True

Note that in (A) and (B) I set default value to View.Details and true.
In (1) And (2) I explicitly set the value to View.LargeIcon and false, after proto-buf's serialization and
de-serialization, we got the wrong value.

The reason is that: for bool value, the default value is false, according to proto-buf's design principle, it will save space when possible, so default value is not saved in the file, only a flag is saved to indicate should use default value(I guess, not verified).

When de-serialization, at first the default constructor is called, and lines (B) is actually part of default constructor at CLR runtime, then the proto-buf de-serialization process kicks up, and found the member m_bool has a default value flag, then use the default value false to set m_bool, which overwrite the default value at (B).

For enum type the reason is similar, in the above example View.LargeIcon is the default value, whose numeric value is 0(verified by Reflected).

To fix it using the DefaultValueAttribute for bool and enum member:

[ProtoContract]
public class MyOption
{
    [ProtoMember(2), DefaultValue(View.Details)]
    public View m_printListView = View.Details;   (A)
    [ProtoMember(5), DefaultValue(true) ]
    public bool m_bool = true ;                   (B)
}

For enum type, there're other issues:
The first one is that all the enumerator should have distinct values, otherwise proto-buf will throw exception when serialization, e.g., the System.Drawing.RotateFlip enum type has following definition:

    public enum RotateFlipType
{
    Rotate180FlipNone = 2,
    Rotate180FlipX = 6,
    Rotate180FlipXY = 0,
    Rotate180FlipY = 4,
    Rotate270FlipNone = 3,
    Rotate270FlipX = 7,
    Rotate270FlipXY = 1,
    Rotate270FlipY = 5,
    Rotate90FlipNone = 1,
    Rotate90FlipX = 5,
    Rotate90FlipXY = 3,
    Rotate90FlipY = 7,
    RotateNoneFlipNone = 0,
    RotateNoneFlipX = 4,
    RotateNoneFlipXY = 2,
    RotateNoneFlipY = 6
}

From image processing's point, RotateNoneFlipNone and Rotate180FlipXY has the same effect, so they have
the same underlying value, it's a reasonable design, however, such a enum cannot works with proto-buf:

    The enum System.Drawing.RotateFlipType has conflicting values RotateNoneFlipNone and RotateNoneFlipNone
Serializer.ThrowInner (Exception exception)
  at ProtoBuf.Serializer.ThrowInner(Exception exception)
  at ProtoBuf.Serializer.Serialize[T](Stream destination, T instance)
  at ProtoBuf.Serializer.SerializeWithLengthPrefix[T](Stream destination, T instance, PrefixStyle style, Int32 tag)

My workaround is to create my own enum and use one-to-one mapping between My_RotateFlipType and System.Drawing.RotateFlipType. Only My_RotateFlipType will be serialized by proto-buf.

public enum RotateFlipType              public enum My_RotateFlipType
{                                       {
    Rotate180FlipNone = 2,                  Rotate180FlipNone,
    Rotate180FlipX = 6,                     Rotate180FlipX,
    Rotate180FlipXY = 0,                    Rotate180FlipXY,
    Rotate180FlipY = 4,                     Rotate180FlipY,
    Rotate270FlipNone = 3,                  Rotate270FlipNone,
    Rotate270FlipX = 7,                     Rotate270FlipX,
    Rotate270FlipXY = 1,                    Rotate270FlipXY,
    Rotate270FlipY = 5,                     Rotate270FlipY,
    Rotate90FlipNone = 1,                   Rotate90FlipNone,
    Rotate90FlipX = 5,                      Rotate90FlipX,
    Rotate90FlipXY = 3,                     Rotate90FlipXY,
    Rotate90FlipY = 7,                      Rotate90FlipY,
    RotateNoneFlipNone = 0,                 RotateNoneFlipNone,
    RotateNoneFlipX = 4,                    RotateNoneFlipX,
    RotateNoneFlipXY = 2,                   RotateNoneFlipXY,
    RotateNoneFlipY = 6                     RotateNoneFlipY
}                                       }

To avoid manually sync with the two data members, I use ProtoBeforeSerialization and OnProtoAfterDeserialization feature to automate it:

[ProtoAfterDeserialization()]
public void OnProtoAfterDeserialization()
{
    Console.WriteLine("called OnProtoAfterDeserialization");
    bool ret = Enum.TryParse(m_rotate.ToString(), out m_rotate_protobuf);
}

[ProtoBeforeSerialization()]
public void OnProtoBeforeSerialization()
{
    Console.WriteLine("called OnProtoBeforeSerialization");
    bool ret = Enum.TryParse(m_rotate_protobuf.ToString(), out m_rotate);
}

The second issue about enum is the 0-value enumerator. If an enum has no enumerator with value 0, then
it's very easy for protobuf to throw exception at runtime.

I will follow the rules when working with protobuf-net:
1. Whenever the default constructor set a value other than default value, use the DefaultValueAttribute.
2. For system or third-party enum type, check it with reflector (statically) or linq (runtime) to see whether it has the above issue, before you add it to protobuf. If conflicts, use the above workaround.

早乙女 2024-09-14 16:17:15

以下对我来说效果很好:

[ProtoContract]
class ClsTest
{
    [ProtoMember(1)]
    public bool IsPeriodic { get; set; }
}

反序列化:

   // stream is a NetworkStream object

   ClsTest clsTestObj = Serializer.DeserializeWithLengthPrefix<ClsTest>(stream, PrefixStyle.Fixed32);
   bool value = clsTestObj.IsPeriodic;

The following works fine for me:

[ProtoContract]
class ClsTest
{
    [ProtoMember(1)]
    public bool IsPeriodic { get; set; }
}

Deserialization:

   // stream is a NetworkStream object

   ClsTest clsTestObj = Serializer.DeserializeWithLengthPrefix<ClsTest>(stream, PrefixStyle.Fixed32);
   bool value = clsTestObj.IsPeriodic;
比忠 2024-09-14 16:17:13

我的猜测是,您的代码将默认 new 实例的 IsPeriodic 设置为 true ,也许:

private bool _isPeriodic = true;

或者,在构造函数中您有:

_isPeriodic = true; // or IsPeriodic = true

基本上,有一个隐式默认值(遵循 protobuf 语言指南),其中 bool 被假定为默认值 false。它不会发送被认为是默认的数据。如果此默认值不正确,请告诉它:

[ProtoMember(1), DefaultValue(true)]

或者 IIRC,您可以尝试将 IsRequired 设置为 true

[ProtoMember(1, IsRequired = true)]

并且还有其他一些方法可以告诉它始终发送值:

private bool ShouldSerializeIsPeriodic() { return true;}

(它使用与核心 .NET 支持的相同模式,用于 PropertyGridXmlSerializerPropertyDescriptor 等 - 这不是我发明随机的模式)

请注意,在“v2”中,我做了两项进一步的更改,以帮助消除这种奇怪的现象:

  • 您可以选择绕过构造函数(WCF 样式),
  • 新的元模型提供了另一种方式来决定是否或不假设默认值

My guess is that your code is setting IsPeriodic to true for default new instances, perhaps:

private bool _isPeriodic = true;

or, in the constructor you have:

_isPeriodic = true; // or IsPeriodic = true

Basically, there is an implicit default (following the protobuf language guide) where-by bool is assumed to have a default of false. It doesn't send data that is believed to be the default. If this default is incorrect, tell it:

[ProtoMember(1), DefaultValue(true)]

or IIRC you can try setting IsRequired to true instead:

[ProtoMember(1, IsRequired = true)]

and there are a few other ways of telling it to always send the value:

private bool ShouldSerializeIsPeriodic() { return true;}

(which uses the same pattern that is supported by core .NET for PropertyGrid, XmlSerializer, PropertyDescriptor, etc - it isn't me inventing random patterns)

Note that in "v2" I have made two further changes to help remove this oddity:

  • you can optionally bypass the constructor (WCF-style)
  • the new meta-model provides another way of deciding whether or not to assume a default
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文