Serialization¶
特徴¶
- アプリケーション側でシリアライザをプラグイン
- MoNo.RAILはメタ形式のみを規定します
- アプリ側でフォーマットを拡張可能です
- MEF(Managed Extensiblity Framework)を利用しています
- バイナリフォーマットです
- テキスト形式に比べ高速でありファイルサイズが小さく済みます
- オブジェクト間のリンク(参照)を表現可能
- 相互参照、循環参照は除く
- シリアライザ毎にフォーマットのバージョンが付与可能
- フォーマットが変わっても後方互換性が保てます
MoNo.RAILの構成¶
MoNo.RAILのシリアライズの仕組みはMoNo.Basic.dllのMoNo.IO名前空間にあるインターフェースなどにより提供されます。
アプリケーション側でシリアライズ対象の MoNo.IO.ISerializer
インターフェースを適切に実装することでシリアライズを実現します。
データ形式¶
保存されるバイナリデータの形式は次のようになります。
上図の各ブロック内の
Content of Object Serialization
の箇所を実装側で書き込んでいくことになります。
オブジェクトのリンク¶
リンクを実装するには参照する側のオブジェクトの内容を書き込む前に参照先のオブジェクトを書き込み、その Object Key
を参照元で書き込むことで実現します。
ISerializerインターフェース¶
では具体的に ISerializer
インターフェースがどうなっているか見てみましょう。
ISerializer
実装クラスは
- TargetType
- TargetGuid
- FormatVersion
の3つのプロパティを持たねばなりません。 また、
- GetDependencies
- Write
- Read
の各メソッドを適切に実装して、データを保存します。
GetDependencies
ではシリアライズ対象のオブジェクトが参照しているオブジェクトを列挙して返します。これによりMoNo.RAILは先にシリアライズすべきオブジェクトを判断でき、後方参照を防ぐことができます。
Write
メソッドの引数として渡される IWritingSession
では GetKey
メソッドが用意されており、既に書き込まれているオブジェクトのObject Key(通し番号)を取得します。
前述した通り、これを書き込んでおくことで読み込み時にリンク先(参照先)のオブジェクト位置を特定できます。
それに対して Read
メソッドの引数として渡される IReadingSession
には GetObject
メソッドが用意されており、Object Keyを渡すことで参照先のオブジェクトを取得できます。
なお、MoNo.IOには AbstractSerializer<'a>
という抽象クラスが用意されており、 TargetType
と FormatVersion
はデフォルト実装が用意されており、TargetType
はtypeof<’a>を返し FormatVersion
は0を返します。
GetDependencies
は空のシーケンスを返す実装となっています。
シリアライザのプラグイン¶
シリアライザが実装されたアセンブリを同じフォルダに置いておくと、MEFの仕組みによりシリアライザがプラグインされます。
MEFの仕組みを利用してシリアライザを見つけられるよう、シリアライザに System.ComponentModel.Composition.Export
属性を typeof<ISerializer>
を引数として設定しておきます。
(System.ComponentModel.Composition.dllへの参照設定が必要です)
open MoNo.IO
open System.ComponentModel.Composition
...
[<Export(typeof<ISerializer>)>]
type MySerializer() =
inherit AbstractSerializer<MyTarget>()
override this.TargetGuid = ...
override this.GetDependencies = ...
override this.WriteTarget(writer, target, session) = ...
override this.ReadTarget(reader, session) = ...
FormatVersion¶
前述した通り、同じターゲットに対して FormatVersion
の異なるシリアライザを定義することができます。
[<Export(typeof<ISerializer>)>]
type MySerializer_Ver0() =
inherit AbstractSerializer<MyTarget>()
...
[<Export(typeof<ISerializer>)>]
type MySerializer_Ver1() =
inherit AbstractSerializer<MyTarget>()
override __.FormatVersion = 1uy
...
MoNo.RAILは書き出しには常に FormatVersion
が最も高いシリアライザを仕様して、シリアライザのバージョン情報をファイルに書き込みます。
読み込み時にはファイルに書かれているバージョンのシリアライザを探し出してデコードを行います。これにより後方互換性が保たれることになります。
リンク(参照)のシリアライズ¶
ドキュメント内に Buzz
クラスのインスタンスを持ち、 Buzz
クラスは Foo
プロパティで Foo
クラスのインスタンスを参照しているというケースを考えてみます。
このドキュメントをシリアライズするために Foo
のシリアライザ FooSerializer
と Buzz
のシリアライザ BuzzSerializer
を実装します。
さて、MoNo.RAILのシリアライザは前方参照のみサポートしますので BuzzSerializer
は Buzz
のインスタンスを書き込む前に参照先の Foo
インスタンスを書き込まなくてはなりません。
そのため、 GetDependencies
メソッドで先に保存すべきオブジェクトとして自身の Foo
を返します。これによりMoNo.RAILは Buzz
インスタンスを書き込む前に参照先のインスタンスが既に書き込まれているかどうかを確認し、書き込まれていなければ先に保存します。
Buzz
インスタンスの書き込み時には参照先に割り当てられたキーを保存しておきます。これにより読み込み時にはキーからオブジェクトを取得し、自身の Foo
プロパティに設定することができます。
[<Export(typeof<ISerializer>)>]
type FooSerializer() =
inherit AbstractSerializer()
...
[<Export(typeof<ISerializer>)>]
type BuzzSerializer() =
inherit AbstractSerializer<Buzz>()
override this.GetDependencies(target:Buzz) =
seq { yield target.Foo :> obj }
override this.WriteTarget(writer, target, session) =
writer.Write(session.GetKey(target.Foo))
...
override this.ReadTarget(reader, session) =
let foo = session.GetObject(reader.ReadInt32()) :?> Foo
...
書き込み/読み込みAPI¶
MoNo.RAILには一つのオブジェクトのみを書き込む、読み込むAPIが用意されています。
namespace MoNo.IO
{
public static class Serialization
{
public static void WriteDocument( string filename, object target );
public static object ReadDocument( string filename );
}
}
複数オブジェクトを入出力する場合は複数オブジェクトをコレクションするドキュメントクラスとそのシリアライザを用意して下さい。