とりあえずジェネリックする

クラスやメソッドをいろいろな型に対応

「ジェネリック」って、クラスやメソッドをいろいろな型に対応させるための仕組み。

いろいろな型に共通する処理を行いたい場合、「object」型を使うという方法がある。object型はあらゆるクラスの基底クラスなのでどの型からも、どの型へも、キャストできる。

ただ、キャストも処理にコストがかかるので多用したくはないし、また値型とobjectとのキャストでは「ボックス化」や「ボックス化解除」というありがたくない処理も入る。

要素をobjectとして格納する「ArrayList」クラスにintの値を出し入れすると↓のようになる。

ArrayList list = new ArrayList();
int i = 1;

list.Add(i);      //【ボックス化】:
                  //iの値「1」を持つオブジェクトがヒープに作られ
                  //その参照がlistに加えられる。
i = (int)list[0]; //【ボックス化解除】:
                  //list[0]の参照先のオブジェクトから「1」が取り出され
                  //その値がiにコピーされる。

ジェネリックのList<int>(int用のListクラス)を使うと

List<int> list = new List<int>();
int i = 1;

list.Add(i); //intをそのまま格納するのでボックス化は起きない
i = list[0]; //intが取り出されるのでボックス化解除はない。
             //キャストも不要。

となって、キャストやボックス化を避けることができる。

ジェネリックを使う

ジェネリックのクラス・メソッドは、名前のうしろに、扱う型(型パラメータ―)を「<」「>」ではさんで付け加え、

【クラス・メソッド名】<【型パラメーター】>

と表記する。なので、さっきの「int用のListクラス」は「List<int>」となっていた。List<int>の元になるジェネリッククラスは「List<T>」として定義されている。

ジェネリックの定義では【型パラメーター】の部分に任意の文字が入る。任意とはいってもたいていは「Type」の頭文字「T」か、「TSource」「TResult」みたいにTのあとにクラス・メソッド内でのその型の役割を表す言葉を付けたものが使われている。

さきほどのList<T>のほか、ジェネリックのキューならQueue<T>、ジェネリックのハッシュテーブルならDictionary<TKey, TValue>などなど。

使うときには先の例のように自分が使いたい型を指定して、その型専用のインスタンスを作る。

List<int> intList = new List<int>();          //int専用List<T>
List<string> stringList = new List<string>(); //string専用List<t>
ジェネリックのクラス・メソッドを作る

ジェネリックのクラスやメソッドを定義するときは

class MyClass<T> {}

みたいに書く。クラスの中味の記述では最初に宣言した「T」を型名として使うことができる。

型パラメータは「,」で区切って、複数指定することもできる。

class MyClass<T1, T2> {}

メソッドだと

TResult DoSomething<TSource, TResult>(TSource source) {}

というふうに入出力それぞれの型パラメータを指定するというのが、よくあるかたち。

扱える型を制約する

どの型でも扱える、というのは便利な反面、どの型でも使える機能しか実装できないということでもある。

T型の2つの変数 t1, t2 を受け取って t1 が t2 に対して小さいか等しいか大きいかを 負値、0、正値 で返すメソッド

public int Compare<T>(T t1, T t2) {}

を作るとして、TがIComparableを実装しているクラスだったら

return t1.CompareTo(t2);

で、話は簡単だけど、そうでなければクラスごとに比較方法を実装しなければならず、すべてのクラスに対応するのはとても無理。

となると、Tに入る型をIComparableを実装したクラスだけに制限したくなる。そういうときは定義の引数リストの丸かっこのあとに

where T : 【許可したい型】

を付加して型パラメーターの制約を行う。また、その場合、

パラメーターの名前に型パラメーターに関する制約を指定することを検討します。
ジェネリック型の型パラメーター (C# プログラミング ガイド)

というガイドラインに従って、Tの名前も変えたほうがそのジェネリックを使う制約が分かって使いやすくなる。

public int Compare<TComparable>(TComparable t1, TComparable t2) where TComparable : IComparable
{
    return t1.CompareTo(t2);
}

参考ページ