【问题标题】:Using bool type in Proto-Buf在 Proto-Buf 中使用 bool 类型
【发布时间】:2010-07-01 16:13:59
【问题描述】:

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

[ProtoContract()]
ClsTest
{
    private bool _isPeriodic

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

        set
        {
            isPeriodic = value;
        }
   }

}

我在我的 collction 对象中使用这个类。

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

【问题讨论】:

    标签: c# protobuf-net


    【解决方案1】:

    我的猜测是,对于默认的 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 样式)
    • 新的元模型提供了另一种方法来决定是否采用默认值

    【讨论】:

      【解决方案2】:

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

      [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 的序列化和 反序列化,我们得到了错误的值。

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

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

      对于枚举类型原因类似,上例中View.LargeIcon为默认值,其数值为0(经Reflected验证)。

      使用布尔和枚举成员的 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之前,先用反射器(静态)或linq(运行时)检查是否有上述问题。如果发生冲突,请使用上述解决方法。

      【讨论】:

        【解决方案3】:

        以下对我来说很好用:

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

        【讨论】:

          【解决方案4】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-03-31
            • 1970-01-01
            • 2014-08-10
            • 2012-01-08
            • 2021-12-10
            • 1970-01-01
            相关资源
            最近更新 更多