プロパティの型コンバータを用意する(層間型変換オブジェクト その2)
昨日のネタの続き
DataExchangeクラスを実装する方法がほぼ固まったので実装に入る。実装で肝になるのは以前にも書いたとおり、変換するオブジェクトのプロパティを変換する部分だ。変換する対象をどこまで広げようか考えたが、当面は リフレクションメソッドである、object.GetType().GetProperties()で引くことが出来、、変換元と変換先の両方に同じ名前で存在しているプロパティだけを変換の対象とすることにした。.NETの場合、これで十分実用だろう。
public virtual void Convert(S source, D dest) { foreach (PropertyInfo info in typeof(S).GetProperties()) { PropertyInfo destPropertyInfo = typeof(D).GetProperty(info.Name); if (destPropertyInfo != null) { /* コンバータによる、プロパティの変換処理 */ } } }
上記のコードで typeof(S).GetProperties()とsource.GetType().GetProperties()では、同じプロパティを引くとは限らないので、注意が必要だ。(自分が嵌ったから書いているわけだが)
・プロパティの(型)コンバータインタフェース
実装する変換操作として、以下のインタフェースを用意する。
public interface IPropertyConverter { void Convert(object source, ref object dest, Type expectType); }
第3パラメタにわざわざ型を指定するのは、destの型と期待する型が同じとは限らないことと、プロパティによっては、destがnullのケースがあるからだ。第2パラメタがrefなのは説明する必要は無いだろう。
IPropertyConverterはプロパティを変換するといいつつも、実はプロパティの型を変換するわけだが、このインタフェースは source -> dest (dest Type)の片方向の変換しか定義していない。IDataExchangeインタフェースは双方向だったのに、こちらはどうして片方向なのだろう。
型コンバータの実装を考えたとき、変換元の型と変換先の型を特定したインタフェースのほうがコンバータの実装としてはシンプルになる可能性があるが、そうしてしまうとコンバータの実装の種類が非常に増えてしまうことに気がついた。(って、普通気がつく。私が愚鈍なだけ)
型A <-> 型B : AtoBConverter 型A <-> 型C : AtoCConverter 型A <-> 型D : AtoDConverter 型B <-> 型A : 型B <-> 型C : 型B <-> 型D : : :どんどん種類が増える
そこで、コンバータは変換先の型毎に実装を用意することにし、それに合わせたインタフェースとしている訳だ。この方法だと、変換元の型を特定し、変換できるか否かを検証しなければならなくなるが、変換先の型の数分の実装を用意すれば済むため、実装するコンバータの数が少なくて済む。
型? -> 型B : BConverter 型? -> 型C : CConverter 型? -> 型D : DConverter
・変換する型の範囲
この手のコンバータでは、型変換をどの範囲まで広げるかが非常に重要。手を抜いて実装すると自動化できる旨みが出ないし、かといって、とことんまで変換対象を広げて、殆ど使わない変換を実装しても時間の無駄だからだ。
今回は実業務で使う可能性が高い型の変換に対象を絞ることにした。
- プロパティの変換対象
同じ型の相互変換(IClonableサポート) プリミティブの相互変換 コレクションの相互変換 ジェネリックコレクションの相互変換 配列とコレクション(ジェネリックコレクション)の相互変換
前にも書いたが、同じ型とプリミティブの相互変換に関しては特に面倒なことは無いだろう。凝るとDate型のように、書式を意識しないとならない型もあるだろうが、殆どは.NETのConvertクラスとTypeConverterが変換してくれる。問題はコレクションだが、この実装に関してはまた次回に書くことにしよう。
追記:
インタフェースは片方向なのだから、それに合わせて変換対象の書き方も片方向にしなくては。
同じ型、及びスーパクラスへの変換(IClonableサポート) プリミティブへの変換 コレクションへの変換 ジェネリックコレクションへの変換 配列への変換
と書きべきか。