「ジェネリック」という言葉がどの次元の言葉なのかはなんか微妙な感じだけど型を引数に取って型を作る機能だと認識する。たとえば、
public class TestGeneric<T> { public TestGeneric(T content, string name) { Content = content; Name = name; } public T Content; public string Name; } class Program { private static void Main(string[] args) { var a = new TestGeneric<int>(66, "sixtysix"); var b = new TestGeneric<string>("hoge", "hoge"); var c = new TestGeneric<TestGeneric<int>>(a, "in the box"); } }
こんな感じ。ジェネリックで作った型の一つなので入れ子にできるのが面白い。
引数に取る型に対していくつかの制約をかけることができる。引数なしのコンストラクタを実装する制約、Tが参照型、または値型である制約、特定のインターフェースを実装または特定の型を継承している制約など。
例をあげるとこんな感じ
public class TestGeneric<T> : IComparer<T> where T : IComparable { public int Compare(T x, T y) { return x.CompareTo(y); } }
型引数Tに対してIComparableインターフェースを実装するように強制している。IComparableインターフェースはthis.CompareTo(hoge)の実装を保証するものだ。これがあるおかげでそれに依存するCompare(T x, T y)が書けるということ。制約と描くと幅を狭めてるように聞こえるが実際はなんらかの保証を付けてると考えたほうがよさそう。制約を書くことによってその保証に依存した処理を書いてもいいようになる(制約つけないと実行時に例外吐いて落ちたりしそう。かといって中でifではじくのも馬鹿らしい)。
メソッドもジェネリック化(こういう使い方であってるのか)できる。制約も可能。
最後に、テンプレートとジェネリックの違いについて。
この二つの違いはどうやらTの方の決定タイミングにあるようだ。C++のテンプレートは実行時に型決定するのに対しC#のジェネリックはコンパイル時に型決定する。なので制約やインターフェース実装などの保証がない限り方に対する情報はゼロなので、中に型情報に依存した実装を書くことができない(たとえばサポートされてるかわからない演算子を使って演算など)。
中でdynamic型でくるんであげればごまかすことはできるけど、微妙なテクニックだと思う。素直に思想に従ったほうがよさそう。
C++に明るくないのでわからないけど、仮に引数型でサポートされてない可能性がある演算子を使った処理を書いた場合、どうなるんだろ。実行時に例外吐かれるのかなぁ……
気が向いたら試してみよう。