アノテーションによる型変換の補助(層間型変換オブジェクト その5)

前回はプロパティのコンバータを実装する所までを書いたが、最後はアノテーションを使ったプロパティ変換を補助する機能の実装を行う。
既に書いたとおり、この機能で二つのオブジェクトを変換する際のルールは以下の通りだった。

  • ある型から、ある型へ、二つの型の間でオブジェクトの変換を行う
  • 互いに、同じ名前を持つプロパティを、変換の対象とする
  • 変換可能な型同士の場合のみ、自動的に変換を行う

- 変換可能な型のパターンは、以下の通り。

  変換元と同じ型、又は継承元への変換(IClonableの場合はクローンする)
  プリミティブへの変換
  コレクションへの変換
  ジェネリックコレクションへの変換
  配列への変換

今回の実装では、アノテーションは上記の変換条件に合致しない、いわばイリーガルなケースに対処する、変換機能を補助する役目を担うことにする。

・名前が違うプロパティを変換対象として指示する
・型コンバータでは変換できない型同士を変換するための、特殊なプロパティのコンバータを指示する

これらを実装するためのアノーテションを実装していく。

過去に何度となく書いているが、.NETにおけるアノテーションはカスタム属性というクラスで簡単に実装することができる。具体的にはSystem.Attributeクラスを継承するだけでよい。なお、クラス名のサフィクスには"Attribute"を付けるのが慣習だが、実際にアノテーションとして記述する際には、このサフィクスは省略することができる。(これは良い決まりごとだ)

[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DxMappingAttribute : Attribute
{
    protected string propertyName;
    protected string targetPropertyName;
    protected Type propertyConverter;
    
    public string PropertyName
    {
        get { return this.propertyName; }
        set { this.propertyName = value; }
    }
    public string TargetPropertyName
    {
        get { return this.targetPropertyName; }
        set { this.targetPropertyName = value; }
    }
    public Type PropertyConverter
    {
        get { return this.propertyConverter; }
        set { this.propertyConverter = value;}
    }
}

アノテーションの役割に当てはめることで、どのプロパティが何の指示を行うものかはすぐ判るだろう。
例えば、型Fooから型Barに変換するDataExchangeクラスを定義するとして、それぞれのクラスに定義されているは、名前の違うプロパティ "FooName"を"BarName"に変換したい場合はIDataExchangeインタフェースの実装クラスで、アノテーションを以下のように記述するのだ。

[DxMappingAttribute(PropertyName="FooName", TargetPropertyName="BarName")]
public class DxOFooToBar : IDataExchange<Foo, Bar>

この場合、PropertyConveterを指定していないので、型変換のルールは型コンバータに委ねられる。
同様に型Fooから型Barへの変換において、型に互換性の無いプロパティ "FooFamily"と、"BarParents"を変換するために、プロパティの型コンバータ"FamilyParentsConverter"クラスを使う場合は以下のように記述する

[DxMappingAttribute(PropertyName="FooFamily", TargetPropertyName="BarParents", PropertyConverter=typeof(FamilyParentsConverter))]
public class DxOFooToBar : IDataExchange<Foo, Bar>

アノテーション(カスタム属性)のプロパティには、Type型も使えるってのがミソだ。このケースではプロパティ"FooFamily"から"BarParents"への変換が、FamilyParentsConverterクラスにより実行される。もちろん、FamilyParentsConverterクラスはIPropertyConverterインタフェースを実装している必要があるが、記述上ではそれをバリデーションすることはできないので、これは実行時に行うことになる。

あとは、DataExchangeクラス内などで、記述されたアノテーションを探して、それに従って処理をするだけだが、記述されたアノテーションを取得するのは、非常に簡単だ。カスタム属性クラスがクラスに対して記述されているのであれば、

Attribute[] attrs = Attribute.GetCustomAttributes(this.GetType(), (typeof(DxMappingAttribute)));

このようなコードで簡単かつ素早くメタデータから、アノテーションの実体(カスタム属性のインスタンス)を取得できるので、こいつを使って、独自の型変換処理を行えばよいだろう。


それにしても、ある機能における特殊なケース、イリーガルなケースに対応する部分をアノテーションで補助する、という方法は非常に応用範囲が広くて、使いでがある。何かパターンとして定義されていないのだろうか。(それが正に"アノーテション(注釈)パターン"なのかもしれないが)