ジェネリック型やジェネリックメソッドではOf句
を使うことであらゆる型をパラメータとして使用できるようになります。 しかし、型パラメータに指定できる型をある程度限定したい場合もあります。 例えば、型パラメータに指定できる型をなんらかのインターフェイスを実装するクラスや、値型・参照型のみにしたい場合などです。 こういった場合には、Of
句に続けてAs
句を加えることで型パラメータに制約を設けることができます。
制約の例
制約の例として、オブジェクトを複製するジェネリックメソッドを作成する例を考えてみます。 ここでは、次のようなメソッドを作成することにします。
CloneObjectメソッドは、引数で与えられたオブジェクトを複製して返すものとします。 このメソッドでは引数objの型および戻り値の型を決定するために型パラメータTObjectを使用します。
上記の動作を満たすようにメソッドの内容を書き換えていきます。 まず、引数objを複製するためにCloneObjectメソッドを使うように書き換えます。 同時に、戻り値の型をTObjectにするためDirectCast演算子でキャストするようにします。
この段階では上記のようにコンパイルエラーが発生します。 objの型はTObjectですが、TObjectにはCloneメソッドが存在しないためこのようなエラーとなります。 これは呼び出し側でTObjectの具体的な型が指定されるまでTObjectがCloneメソッドを持つかどうかわからないためです。
このエラーを回避するため、型パラメータTObjectに制約を指定することにします。 型TObjectがCloneメソッドを持つ型に限定されるようにするため、「TObjectはICloneableインターフェイスを実装していなければならない」という制約を設けます。 具体的には、メソッド宣言部のOf
句に続けてAs ICloneable
を追加します。
このように制約を設けることで、ジェネリックメソッド内では型パラメータが実装するICloneableインターフェイスのメンバ(=Cloneメソッド)を呼び出すことができるようになり、同時に型パラメータに指定できる型をICloneableインターフェイスを実装する型に限定することができるようになります。 一方、制約に反する型を指定してこのメソッドを呼び出そうとした場合にはコンパイルエラーとなります。
インターフェイスの制約
先の例のように、制約には通常のインターフェイスを指定できるほか、ジェネリックなインターフェイスを指定することもできます。 例えば、IComparable(Of T)インターフェイスを実装する二つの引数のうち大きい方を返すMaxメソッドを作成すると次のようになります。 このメソッドでは、型パラメータTに「IComparable(Of T)インターフェイスを実装していること」を制約として求めています。
クラスの制約
制約にはインターフェイスだけでなく次の例のように基底クラスを指定することもできます。
ただ、この例のような場合、引数の型を単に基底クラスの型にすればよいので制約を用いる必要性はありません。
このように、ジェネリクスを使わなくてもポリモーフィズムによって実現できる場合と、ジェネリクスと制約を使わなければ実現できない場合があるため、両者を適切に使い分ける必要があります。
値型・参照型の制約
インターフェイスやクラスなどの具体的な型による制約だけでなく、値型または参照型のどちらかに限定する制約を指定することもできます。 型パラメータが値型であることを要求する場合はOf
句に続けてAs Structure
、参照型であることを要求する場合は続けてAs Class
を指定します。
値型・参照型と制約については値型と参照型 §.ジェネリック型の制約でも解説しています。
デフォルト値
ジェネリックメソッド・ジェネリック型で総称型(=パラメータ化された型)の値を初期化する場合、値型か参照型かによって場合分けする必要はなく、ともにNothingを代入することで初期化できます。 例えば次のような配列の内容をクリアするジェネリックメソッドを考えます。
上記の例では、参照型用と値型用の二つのジェネリックメソッドを用意しています。 この例の場合、制約によって値型・参照型に限定するまでもなくどちらもNothingの代入によりデフォルト値への初期化を行うことができます。
値型・参照型とデフォルト値については型の種類・サイズ・精度・値域 §.型のデフォルト値、値型(構造体)の初期化については構造体でも解説しています。
コンストラクタの制約
制約により、パラメータ化された型が必ずコンストラクタを持つように要求することもできます。 Of
句に続けてAs New
と指定することにより、型パラメータにコンストラクタを持つ型のみを指定できるように限定することができます。 ただし、この制約により指定できるのは引数を取らないコンストラクタ(デフォルトコンストラクタ)のみとなります。 引数を取るコンストラクタを制約で指定することはできません。
このように、制約でAs New
を指定した場合はデフォルトコンストラクタを持つクラスまたは暗黙的にデフォルトコンストラクタが用意される構造体のみが型パラメータとして指定できるようになります。
制約の組み合わせ
ここまでで紹介してきた制約を複数組み合わせて指定することもできます。 複数の制約を指定する場合は、Of T As {制約1, 制約2, ...}
のように中括弧でくくって指定します。 例えば次の制約の場合、「型パラメータTは参照型(Class
)で、デフォルトコンストラクタを持ち(New
)、かつIDisposableインターフェイスを実装している」ことを要求します。
制約と列挙体
値型(構造体)・参照型(インターフェイス・クラス)に対応する制約は存在しますが、列挙体(Enum)に対応する制約は存在しません。 そのため、型パラメータに指定できる型を列挙型に限定したい場合は、列挙体の基底型である構造体(Structure)を制約として指定します。 当然これだけでは制約としては不十分なので、必要に応じてメソッド内で型情報を調べて実際に指定された型が列挙体かどうかを調べるようにすることもできます。