Я обнаружил, что работа с типами 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);
}
}
Теперь угадайте вывод.Это:
Details
True
Обратите внимание, что в (A) и (B) я установил значение по умолчанию для View.Details и true.В (1) и (2) я явно устанавливаю значение View.LargeIcon и false, после сериализации и десериализации proto-buf мы получили неправильное значение.
Причина в том, что: для значения boolзначение по умолчанию - false, в соответствии с принципом разработки proto-buf, оно будет экономить место, когда это возможно, поэтому значение по умолчанию не сохраняется в файле, сохраняется только флаг, указывающий, что следует использовать значение по умолчанию (я думаю, не проверено).
При десериализации сначала вызывается конструктор по умолчанию, а линии (B) фактически являются частью конструктора по умолчанию во время выполнения CLR, затем запускается процесс десериализации proto-buf и обнаруживаетсяЭлемент m_bool имеет флаг значения по умолчанию, затем используйте значение по умолчанию false, чтобы установить m_bool, который перезаписывает значение по умолчанию в (B).
Для типа enum причина аналогична, в приведенном выше примере View.LargeIcon isзначение по умолчанию, числовое значение которого равно 0 (проверено с помощью Reflected).
Чтобы исправить это, используйте DefaultValueAttribute для bool и 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)
}
Для типа 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. * 1024 *
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);
}
ВторойВопрос о enum - это перечислитель с 0 значениями.Если enum не имеет перечислителя со значением 0, то для protobuf очень легко вызвать исключение во время выполнения.
Я буду следовать правилам при работе с protobuf-net: 1. Всякий раз, когда конструктор по умолчанию устанавливает значение otherчем значение по умолчанию, используйте DefaultValueAttribute.2. Для системного или стороннего типа enum, проверьте его с помощью отражателя (статически) или linq (время выполнения), чтобы увидеть, есть ли у него вышеуказанная проблема, прежде чем добавлять его в protobuf.Если конфликтует, используйте вышеуказанный обходной путь.