我发现使用 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(运行时)检查是否有上述问题。如果发生冲突,请使用上述解决方法。