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

更新 : 2007 年 11 月

ジェネリック クラスは、特定のデータ型に固有ではない操作をカプセル化します。一般に、ジェネリック クラスは、リンクされたリスト、ハッシュ テーブル、スタック、キュー、ツリーなどのコレクションと共に使用されます。コレクション内の項目の追加や削除などの操作は、格納されるデータ型にかかわらず、基本的に同じ方法で実行されます。

コレクション クラスが必要な場合、一般に、.NET Framework クラス ライブラリに用意されているものを使用することをお勧めします。これらのクラスの使用方法の詳細については、「.NET Framework クラス ライブラリのジェネリック (C# プログラミング ガイド)」を参照してください。

通常、ジェネリック クラスを作成するには、既存の具象クラスから始め、生成と使いやすさが最適なバランスになるまで、1 つずつ、型を型パラメータに変更します。独自にジェネリック クラスを作成する場合は、次の点を考慮する必要があります。

  • 型パラメータに一般化する型。

    一般に、パラメータ化できる型が増えると、コードの柔軟性と再利用できる度合いが向上します。ただし、一般化しすぎると、他の開発者が読んだり理解したりするのが困難なコードになります。

  • 型パラメータに適用する制約 (存在する場合) の内容 (「型パラメータの制約 (C# プログラミング ガイド)」を参照してください)。

    望ましい規則は、必要に応じて型を処理できる範囲で、最大の制約を適用することです。たとえば、参照型でのみジェネリック クラスを使用することがわかっていれば、そのクラスの制約を適用します。こうすることで、値型でクラスを使うという意図しない用法を回避できます。また、T で as 演算子を使用し、null 値をチェックできるようになります。

  • ジェネリックの動作を基本クラスとサブクラスにファクタリングするかどうか。

    ジェネリック クラスは基本クラスとして機能するため、非ジェネリック クラスと同様なデザインの考慮事項が適用されます。ジェネリックな基本クラスからの継承に関する規則については、後述します。

  • 1 つ以上のジェネリック インターフェイスを実装するかどうか。

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

単純なジェネリック クラスの例については、「ジェネリックの概要 (C# プログラミング ガイド)」を参照してください。

型パラメータの規則と制約には、ジェネリック クラスの動作 (特に、継承とメンバのアクセシビリティ) について、暗示的な意味合いがあります。次に進む前に、いくつかの用語を理解しておく必要があります。ジェネリック クラスで、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()
{ }

オープン構築型とクローズ構築j型は、メソッドのパラメータとして使用できます。

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> を提示しようとすると、コンパイル時にエラーが発生します。

参照

概念

C# プログラミング ガイド

参照

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

System.Collections.Generic

その他の技術情報

Saving the State of Enumerators

An Inheritance Puzzle, Part One