Serialization

特徴

  • アプリケーション側でシリアライザをプラグイン
    • MoNo.RAILはメタ形式のみを規定します
    • アプリ側でフォーマットを拡張可能です
    • MEF(Managed Extensiblity Framework)を利用しています
  • バイナリフォーマットです
    • テキスト形式に比べ高速でありファイルサイズが小さく済みます
  • オブジェクト間のリンク(参照)を表現可能
    • 相互参照、循環参照は除く
  • シリアライザ毎にフォーマットのバージョンが付与可能
    • フォーマットが変わっても後方互換性が保てます

MoNo.RAILの構成

../../_images/serialize_module.png

MoNo.RAILのシリアライズの仕組みはMoNo.Basic.dllのMoNo.IO名前空間にあるインターフェースなどにより提供されます。

../../_images/serialize_plugin.png

アプリケーション側でシリアライズ対象の MoNo.IO.ISerializer インターフェースを適切に実装することでシリアライズを実現します。

データ形式

保存されるバイナリデータの形式は次のようになります。

../../_images/serialize_data_structure.png

上図の各ブロック内の Content of Object Serialization の箇所を実装側で書き込んでいくことになります。

オブジェクトのリンク

../../_images/serialize_link.png

リンクを実装するには参照する側のオブジェクトの内容を書き込む前に参照先のオブジェクトを書き込み、その Object Key を参照元で書き込むことで実現します。

ISerializerインターフェース

では具体的に ISerializer インターフェースがどうなっているか見てみましょう。

../../_images/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> という抽象クラスが用意されており、 TargetTypeFormatVersion はデフォルト実装が用意されており、TargetType はtypeof<’a>を返し FormatVersion は0を返します。 GetDependencies は空のシーケンスを返す実装となっています。

シリアライザのプラグイン

../../_images/serialize_plugin_load.png

シリアライザが実装されたアセンブリを同じフォルダに置いておくと、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 が最も高いシリアライザを仕様して、シリアライザのバージョン情報をファイルに書き込みます。 読み込み時にはファイルに書かれているバージョンのシリアライザを探し出してデコードを行います。これにより後方互換性が保たれることになります。

リンク(参照)のシリアライズ

../../_images/serialize_reference.png

ドキュメント内に Buzz クラスのインスタンスを持ち、 Buzz クラスは Foo プロパティで Foo クラスのインスタンスを参照しているというケースを考えてみます。

このドキュメントをシリアライズするために Foo のシリアライザ FooSerializerBuzz のシリアライザ BuzzSerializer を実装します。 さて、MoNo.RAILのシリアライザは前方参照のみサポートしますので BuzzSerializerBuzz のインスタンスを書き込む前に参照先の 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 );
    }
}

複数オブジェクトを入出力する場合は複数オブジェクトをコレクションするドキュメントクラスとそのシリアライザを用意して下さい。

../../_images/serialize_read_write.png