ジェネリック クラス (C# プログラミング ガイド)

ジェネリック クラスは、特定のデータ型に固有ではない操作をカプセル化します。 ジェネリック クラスは最も一般的に、リンク リスト、ハッシュ テーブル、スタック、キュー、ツリーなどのコレクションと共に使用されます。 コレクションの項目を追加または削除するなどの操作は、保存されているデータの型に関係なく、基本的に同じように実行されます。

コレクション クラスを必要とするほとんどのシナリオで、.NET クラス ライブラリで提供されているものを使用するという方法が推奨されます。 これらのクラスの使用の詳細については、「.NET のジェネリック コレクション」を参照してください。

通常、ジェネリック クラスを作成するには、既存の具象クラスから始め、汎用性と使いやすさの間で最適なバランスが取れるまで、一度に 1 つずつ型を型パラメーターに変更します。 独自のジェネリック クラスを作成するときの重要な考慮事項は次のとおりです。

  • 型パラメーターに汎用化する型。

    通例、パラメーター化できる型が多ければ多いほど、コードの柔軟性が上がり、再利用しやすくなります。 ただし、汎用化が多すぎると、他の開発者にとって読みにくい、理解しにくいコードが生成されます。

  • 型パラメーターに適用する制約 (制約がある場合) (「型パラメーターの制約」を参照)。

    処理しなければならない型を処理できる範囲で最大の制約を適用することが推奨されます。 たとえば、ジェネリック クラスが参照型でのみ使用される場合、クラス制約を適用します。 それにより、意図しない、値型でのクラスの使用が回避され、Tas 演算子を使用したり、null 値を確認したりできます。

  • 基底クラスやサブクラスの要因としてジェネリック動作を考慮するかどうか。

    ジェネリック クラスは基底クラスとして機能できるので、非ジェネリック クラスと同様の設計考慮事項がここで適用されます。 ジェネリック基底クラスからの継承ルールについて、このトピックの後半で確認してください。

  • 1 つまたは複数のジェネリック インターフェイスを実装するかどうか。

    たとえば、ジェネリック基盤のコレクションで項目を作成するために使用されるクラスを設計するとき、場合によっては、IComparable<T> のようなインターフェイスを実装する必要があります。ここで T はクラスの型です。

単純なジェネリック クラスの例については、「ジェネリックの概要」を参照してください。

型パラメーターや制約のルールは、特に継承とメンバーのアクセシビリティに関して、ジェネリック クラスの動作と密接な関係があります。 続行する前に、いくつかの用語を理解してください。 ジェネリック クラス Node<T>, について、クライアント コードでは、型引数を指定する (クローズ構築型を作成する (Node<int>)) か、型パラメーターを指定しない (たとえば、ジェネリック基底クラスを指定する場合、オープン構築型を作成する (Node<T>)) ことで、クラスを参照できます。 ジェネリック クラスは、具象、構築されたクローズ型、または構築されたオープン型の基底クラスから継承できます。

class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }

非ジェネリック クラス、言い換えれば、具象クラスは、構築されたクローズ型の基底クラスから継承できますが、構築されたオープン型のクラスや型パラメーターからは継承できません。ランタイム時、基底クラスのインスタンス化に必要な型引数をクライアント コードが提供できないためです。

//No error
class Node1 : BaseNodeGeneric<int> { }

//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}

構築されたオープン型から継承するジェネリック クラスは、継承クラスで共有されない基底クラスの型パラメーターに対して型引数を提供する必要があります。次のコードをご覧ください。

class BaseNodeMultiple<T, U> { }

//No error
class Node4<T> : BaseNodeMultiple<T, int> { }

//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }

//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

構築されたオープン型から継承するジェネリック クラスは、基底クラスの制約のスーパーセットである (基底クラスの制約を暗黙に定義する) 制約を指定する必要があります。

class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

ジェネリック型では、次のように、複数の型パラメーターと制約を使用できます。

class SuperKeyType<K, V, U>
    where U : System.IComparable<U>
    where V : new()
{ }

構築されたオープン型と構築されたクローズ型をメソッド パラメーターとして使用できます。

void Swap<T>(List<T> list1, List<T> list2)
{
    //code to swap items
}

void Swap(List<int> list1, List<int> list2)
{
    //code to swap items
}

ジェネリック クラスでインターフェイスを実装する場合、そのクラスのすべてのインスタンスをそのインターフェイスにキャストできます。

ジェネリック クラスは変化しません。 言い換えると、入力パラメーターが List<BaseClass> を指定するとき、List<DerivedClass> を指定するとコンパイル時エラーが表示されます。

関連項目