シリアライザ覚書(参)

シリアライザの一つである、Protocol Buffers(以下、ProtoBuf)の調査で、気づいた点、というかトラブルの元になるであろう性質をメモっておく。
○DataMemberのOrderを消すと、正常にシリアライズしない。エラーもなし。
その弐で使ったクラスのDataMemberからOrderを消すと、ProtoBufでは期待通りの結果にはならない。エラーにもならない。
namespace TawamureDays {

/// <summary>
/// シリアライズ対象用クラス
/// </summary>
[DataContract()]
[ProtoBuf.ProtoContract(UseProtoMembersOnly=false)]
public class TwClass1 {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwClass1() {
this.Text = string.Empty;
this.Date = DateTime.Today;
return;
}

#endregion

#region プロパティ

/// <summary>文字列</summary>
[DataMember()]
public string Text { get; set; }

/// <summary>整数値</summary>
[DataMember()]
public int Integer { get; set; }

/// <summary>数値</summary>
[DataMember()]
public decimal Numeric { get; set; }

/// <summary>日付</summary>
[DataMember()]
public DateTime Date { get; set; }

#endregion
}
}
DataContractシリアライザでは、きちんとできる。ProtBufでは、すべて初期値になる。要するにシリアライズできないという事。エラーにならないといのが非常に困る所。NotSupprtでもInvalidIOperationでも良いから例外になってくれれば気づくのに…。
○DataMemberのOrderを0からにすると、Order=0のプロパティだけシリアライズしない。エラーも無し。
namespace TawamureDays {

/// <summary>
/// シリアライズ対象用クラス
/// </summary>
[DataContract()]
[ProtoBuf.ProtoContract(UseProtoMembersOnly=false)]
public class TwClass1 {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwClass1() {
this.Text = string.Empty;
this.Date = DateTime.Today;
return;
}

#endregion

#region プロパティ

/// <summary>文字列</summary>
[DataMember(Order=0)]
public string Text { get; set; }

/// <summary>整数値</summary>
[DataMember(Order=1)]
public int Integer { get; set; }

/// <summary>数値</summary>
[DataMember(Order=2)]
public decimal Numeric { get; set; }

/// <summary>日付</summary>
[DataMember(Order=3)]
public DateTime Date { get; set; }

#endregion
}
}
Textというプロパティだけが初期値になって、ちゃんとシリアライズできない。Textがnullのまま動作確認すると、できていると勘違いしてしまうおそろしいトラップ(?)である。

○DataMemberのOrderを重複させると、InvalidOperationが発生する。
namespace TawamureDays {

/// <summary>
/// シリアライズ対象用クラス
/// </summary>
[DataContract()]
[ProtoBuf.ProtoContract(UseProtoMembersOnly=false)]
public class TwClass1 {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwClass1() {
this.Text = string.Empty;
this.Date = DateTime.Today;
return;
}

#endregion

#region プロパティ

/// <summary>文字列</summary>
[DataMember(Order=0)]
public string Text { get; set; }

/// <summary>整数値</summary>
[DataMember(Order=1)]
public int Integer { get; set; }

/// <summary>数値</summary>
[DataMember(Order=2)]
public decimal Numeric { get; set; }

/// <summary>日付</summary>
[DataMember(Order=2)]
public DateTime Date { get; set; }

#endregion
}
}
ProtoMember属性がないときは、DataMemberのOrderを代用しているらしい。
しかし、ProtoMemberのTag(Id)はユニーク必須なので、重複しているよというエラーになる。

System.InvalidOperationException: Duplicate field-number detected; 2 on: TawamureDays.TwClass1


○正しいようで正しくない。
TwConcreteClass1をシリアライズしても、TextInBaseプロパティをシリアライズしない。
namespace TawamureDays {
/// <summary>
/// シリアライズ対象用基底クラス
/// </summary>
[ProtoBuf.ProtoContract(UseProtoMembersOnly=true)]
public class TwBaseClass {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwBaseClass() {
this.TextInBase = string.Empty;
return;
}

#endregion

#region プロパティ

/// <summary>文字列</summary>
[ProtoBuf.ProtoMember(1)]
public string TextInBase { get; set; }

#endregion
}

/// <summary>
/// シリアライズ対象用実装クラス
/// </summary>
[ProtoBuf.ProtoContract(UseProtoMembersOnly=true)]
public class TwConcreteClass1 : TwBaseClass {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwConcreteClass1() : base() {
return;
}

#endregion

#region プロパティ

/// <summary>数値</summary>
[ProtoBuf.ProtoMember(1)]
public int NumberInConcrete { get; set; }

#endregion
}
}
基底クラスに、ProtoInclude属性を付ける必要がある。

/// <summary>
/// シリアライズ対象用基底クラス
/// </summary>
[ProtoBuf.ProtoContract(UseProtoMembersOnly=true)]
[ProtoBuf.ProtoInclude(2, typeof(TwConcreteClass1))]
public class TwBaseClass {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwBaseClass() {
this.TextInBase = string.Empty;
return;
}

#endregion

#region プロパティ

/// <summary>文字列</summary>
[ProtoBuf.ProtoMember(1)]
public string TextInBase { get; set; }

#endregion
}
第一引数の2は固定ではなく、ProtoMemberの第一引数と重複しないようにする。継承する度に属性を付けるのか!という気分になれる。上記の設定は、同じアセンブリにあるという前提でできる事なので、共通ライブラリとして公開されたアセンブリの中のクラスをベースにしようとすると、コード上で行う必要がある。
var typeModel = ProtoBuf.Meta.RuntimeTypeModel.Default;
var metaType = typeModel.Add(typeof(TwBaseClass), true);
metaType.AddSubType(2, typeof(TwConcreteClass1));
AddSubTypeの第一引数は、ProtoMemberと重複しないようにする。重複するとやはりエラーになる。

○ProtoBuf.ServiceModel内のシリアライザでは、Nullable系のプロパティをシリアライズできない。
正確には、シリアライズしたファイルからデシリアライズしようとした時にエラーになる。ProtoBuf内のシリアライザはエラーにならない。ProtoBuf.ServiceModel内のシリアライザは、WCFで使う事になっているProtoBehaviorが使っている筈なんだけど、これって要するに、WCFで使うと、ProxyクラスにNullable系のプロパティを設定できない事になるのではないだろうか…。Stackoverflowさんにも記事がちらほら。
Problems serializing a list of nullable doubles using Protobuf-net
うーん。性能がいいだけにもったいないなぁ。
あと、非公式マニュアルなんてものもある。
Protobuf-net: the unofficial manual
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m