在Proto-Buf中使用bool类型

时间:2010-07-01 16:14:00

标签: c# protobuf-net

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

[ProtoContract()]
ClsTest
{
    private bool _isPeriodic

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

        set
        {
            isPeriodic = value;
        }
   }

}

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

序列化过程工作正常,但在反序列化后,默认情况下属性IsPeriodic的值为true,尽管在某些情况下它是假的。任何人都可以帮助我吗?

4 个答案:

答案 0 :(得分:6)

我的猜测是,您的代码正在为默认IsPeriodic个实例设置truenew,可能是:

private bool _isPeriodic = true;

或者,在构造函数中:

_isPeriodic = true; // or IsPeriodic = true

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

[ProtoMember(1), DefaultValue(true)]

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

[ProtoMember(1, IsRequired = true)]

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

private bool ShouldSerializeIsPeriodic() { return true;}

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

请注意,在“v2”中,我进行了两次更改以帮助消除这种奇怪现象:

  • 您可以选择绕过构造函数(WCF样式)
  • 新的元模型提供另一种决定是否采用默认的方式

答案 1 :(得分:1)

我发现使用bool和enum类型与protobuf-net有一些技巧。 关于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);
    }
}

现在,猜猜输出。它&#39; S:

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)处的默认值。

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

使用bool和enum成员的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(运行时)检查它是否存在上述问题。如果发生冲突,请使用上述解决方法。

答案 2 :(得分:0)

以下对我来说很好:

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

答案 3 :(得分:0)

我遇到了类似的问题,默认为bool的{​​{1}}成员未从配置文件中读取其值。

true