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