3

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

[ProtoContract()]
ClsTest
{
    private bool _isPeriodic

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

        set
        {
            isPeriodic = value;
        }
   }

}

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

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?

4

4 回答 4

7

我的猜测是您的代码设置IsPeriodictrue默认new实例,也许:

private bool _isPeriodic = true;

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

_isPeriodic = true; // or IsPeriodic = true

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

[ProtoMember(1), DefaultValue(true)]

或 IIRC,您可以尝试设置IsRequiredtrue

[ProtoMember(1, IsRequired = true)]

还有其他几种方法可以告诉它始终发送值:

private bool ShouldSerializeIsPeriodic() { return true;}

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

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

  • 您可以选择绕过构造函数(WCF 样式)
  • 新的元模型提供了一种决定是否假设默认值的方法
于 2010-07-01T22:52:09.677 回答
1

我发现使用 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)中,我显式地将值设置为 View.LargeIcon 和 false,经过 proto-buf 的序列化和反序列化后,我们得到了错误的值。

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

反序列化时,首先调用默认构造函数,而行(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)
}

对于 enum 类型,还有其他问题: 第一个是所有枚举数都应该有不同的值,否则 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(运行时)检查是否有上述问题。如果发生冲突,请使用上述解决方法。

于 2014-05-23T06:13:14.680 回答
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;
于 2010-07-01T19:07:17.473 回答
0

我有一个类似的问题,bool默认的成员true没有从配置文件中读取他们的值。

[ProtoMember(1, IsRequired=true), DefaultValue(true)]
public bool IsPeriodic
...
于 2015-01-15T15:22:14.277 回答