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 の設計原則に従って、可能な場合はスペースを節約するため、デフォルト値はファイルに保存されず、デフォルトを使用する必要があることを示すフラグのみが保存されます。値(私は推測しますが、検証されていません)。
逆シリアル化では、最初に既定のコンストラクターが呼び出され、行 (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)
}
列挙型の場合、他にも問題があります。最初の 1 つは、すべての列挙子が個別の値を持つ必要があることです。そうしないと、シリアライズ時に 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 の間で 1 対 1 のマッピングを使用することです。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
} }
2 つのデータ メンバーと手動で同期することを避けるために、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);
}
列挙型に関する 2 番目の問題は、値が 0 の列挙子です。列挙型に値 0 の列挙子がない場合、実行時に protobuf が例外をスローするのは非常に簡単です。
protobuf-net を使用するときは、次のルールに従います。 1. デフォルト コンストラクターがデフォルト値以外の値を設定する場合は常に、DefaultValueAttribute を使用します。2. システムまたはサードパーティの列挙型の場合、protobuf に追加する前に、リフレクター (静的) または linq (ランタイム) で上記の問題があるかどうかを確認します。競合する場合は、上記の回避策を使用してください。