ここでは複素数を定義する次のような構造体を使って、ユーザー定義の型変換について見ていきます。
.NET Framework 4よりSystem.Numerics.Complex構造体が使えるようになっているため、通常このような構造体を独自に定義する必要性はあまりありませんが、ここではユーザー定義の型変換の例として独自の複素数型を取り上げています。 .NET Frameworkで提供されるComplex構造体については複素数型で解説しています。
文字列への変換
ToString
文字列への変換をサポートするためには、ToStringをオーバーライドすることが出来ます。
もちろん、次の例ように妥当な理由がある場合は文字列化専用のメソッドを用意しToStringメソッドをオーバーライドしないという方法を採ることもできます。 ただ、すべてのユーザー定義型はObjectを継承していて、ランタイムにより文字列化される際にObject.ToStringメソッドが呼び出されることがあるため、このメソッドをオーバーライドすることで文字列化される際の動作を定義しておいた方がよいでしょう。
IFormattable
IFormattableインターフェイスを実装することで、書式を指定した文字列化をサポートすることができます。 また、書式だけでなくカルチャを考慮した文字列化のサポートもIFormattableインターフェイスを用いて実装出来ます。 IFormattableについての詳細は書式の定義と実装で解説しています。
基本型への変換
ToXXX
他の基本型への変換を定義するもっとも簡単な方法は、文字列化の際と同様、目的の型への変換用のメソッドを用意することです。 次の例では、Complex型をDouble型の値として返すメソッドToDoubleを用意し、複素数の絶対値を返すようにしています。
ただ、このようなメソッドを用意しても、Convertクラスを使った変換などが出来るようになるわけではありません。 次のコードを実行すると、例外InvalidCastExceptionがスローされます。
IConvertible
Convertクラスを用いた型変換をサポートするには、IConvertibleインターフェイスを実装しなければなりません。 このインターフェイスには、他の基本型への変換を行うためのメソッドが用意されています。 これらのメソッドでは、変換を定義できる場合は変換結果を返し、変換を定義できない場合はInvalidCastExceptionをスローするようにします。
以下はIConvertibleを実装する例です。 この例のComplex型では、IConvertibleを次のように実装しています。
- Double型への変換では、Complex型の表す複素数の絶対値を返す
- その他の実数型・整数型への変換では、Complex型の表す複素数の絶対値を目的の型に変換して返す
- Boolean型の変換では、実部・虚部ともに0の場合はfalse、そうでなければtrueを返す
- String型への変換では、ガウス平面座標表示に書式化した文字列を返す
- System.Drawing.PointF型への変換をサポートする
- それ以外の型への変換はサポートしない(InvalidCastExceptionをスローする)
- IConvertible.ToString以外は直接呼び出せないように明示的な実装にする
暗黙的・明示的な型変換
ここまでではメソッドやインターフェイスを通した型変換を行う例を解説してきましたが、型変換演算子をオーバーロード(多重定義)することで型変換演算子(キャスト演算子、CType)を用いた型変換をサポート出来るようになります。
暗黙的な型変換(拡大変換)演算子のオーバーロード
C#ではimplicit operator、VBではWidening Operator CTypeを用いることで暗黙の型変換演算子をオーバーロード出来ます。 この演算子は、拡大変換を定義する場合にオーバーロードします。 変換先の型は必ずしも基本型である必要は無く、変換が定義できるならどのような型への変換も実装出来ます。 また、変換先の型を複数定義することも出来ます。
次の例は、Complex型を暗黙的にDouble型およびString型へと変換できるよう演算子をオーバーロードする例です。
型変換演算子となるメソッドはpublic static/Public Sharedでなければなりません。 また、暗黙の型変換演算子のオーバーロードする際にはいくつかの注意が必要です。
暗黙の型変換演算子がオーバーロードされている場合、異なる型の変数への意図しない代入でも変換が行われてしまうことになります。 上記の例では、Complex型から暗黙的にString型へ変換することが出来るようになっていますが、これは他の多くの基本型とは異なる動作であるため、混乱を招く可能性があります。 このような場合の他、変換によりデータの欠損や桁落ち、オーバーフローが発生する場合は、代わりに明示的な型変換演算子を用いるべきです。 言い換えると、暗黙の変換が行われても予期しない結果となることもなく、変換により例外が発生しないような場合には、暗黙の型変換演算子をオーバーロードしても問題にはならないと言えます。
明示的な型変換(縮小変換)演算子のオーバーロード
C#ではexplicit operator、VBではNarrowing Operator CTypeを用いることで明示的な型変換演算子をオーバーロード出来ます。 この演算子は、縮小変換を定義する場合にオーバーロードします。 暗黙的な型変換演算子と同様、変換先の型は必ずしも基本型である必要は無く、変換が定義できるならどのような型への変換も実装出来ます。 また、変換先の型を複数定義することも出来ます。
次の例は、Complex型を明示的にInteger型およびPointF型へと変換できるよう演算子をオーバーロードする例です。
暗黙的な型変換演算子と同様、明示的な型変換演算子となるメソッドはpublic static
/Public Shared
でなければなりません。 また、暗黙的な型変換演算子とは異なり、変換に際してOverflowException、InvalidCastException、FormatExceptionなどの例外をスローすることが出来ます。
上記の例では、非数もしくは無限大の値を持つ場合、Integer型への変換時にOverflowExceptionをスローするようにしていますが、値が変換できない形式ならFormatException、定義できない変換であればInvalidCastExceptionをスローするように出来ます。
相互変換が可能な型でのオーバーロード
二つの型で型変換演算子をオーバーロードすることで、型を相互に変換できるようにすることが出来ます。 以下の例では、直交形式と極形式の二つの形式で複素数を表現する型を用意し、相互に変換できるよう演算子をオーバーロードしています。
この例では、CartesianComplexからPolarComplexへの変換はCartesianComplexで、PolarComplexからCartesianComplexへの変換はPolarComplexで定義していますが、型変換演算子をオーバーロードする場合は必ずしも変換元の型に実装しなければならないというわけではなく、変換前・変換先の型ならどちらでも定義することが出来ます。 次の例では、先の例での型変換演算子の両方をPolarComplexでオーバーロードしています。
当然ながら、型変換演算子からは他の型の非パブリックなメンバにはアクセス出来ないので、この例の様な方法が採れる場合は多くはありません。
その他
Converter<TInput, TOutput>
Converterジェネリックデリゲートは任意の型変換を行うメソッドを表すデリゲートです。 このデリゲートは、Array.ConvertAllやList.ConvertAllなどのメソッドで、特定の型から別の型に変換する場合に使われます。 型パラメータTInputとTOutputで変換前と変換後の型を任意に指定することが出来ます。 このデリゲートと変換を行うメソッドを組み合わせて使うことで、型変換演算子が定義されていない・Convertクラスで変換できない場合や異なる変換ルールを使用したい場合に、独自に変換処理を定義して型の変換を行えるようになります。
次の例では、Converterデリゲートを使って複素数Complex型の配列をPoint型の配列に変換しています。