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

ジェネリック型またはメソッドが CIL (共通中間言語) にコンパイルされるとき、型パラメーターありとして識別するメタデータが追加されます。 ジェネリック型の CIL の使われ方は、指定した型パラメーターの種類 (値型または参照型) によって異なります。

ジェネリック型が値型をパラメーターとして最初に構築されるとき、ランタイムにより、特殊なジェネリック型が作成されます。このとき、CIL の適切な場所で指定のパラメーターが代わりに使用されます。 特殊なジェネリック型は、パラメーターとして使用される一意の値型ごとに 1 回作成されます。

たとえば、プログラム コードで、整数で構成されるスタックを宣言したとします。

Stack<int>? stack;

この時点で、整数がそのパラメーターに合わせて置き換えられた Stack<T> クラスの特殊なバージョンがランタイムにより生成されます。 プログラム コードで整数のスタックを使用するたびに、ランタイムは、生成された特殊な Stack<T> クラスを再利用します。 次の例では、整数のスタックの 2 つのインスタンスが作成され、Stack<int> コードの単一インスタンスを共有します。

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

ただし、long のような異なる値型を持つか、パラメーターとしてユーザー定義構造を持つ別の Stack<T> クラスがコード内の別のポイントで作成されると想定します。 結果として、ランタイムによりジェネリック型の別バージョンが生成され、CIL 内の適切な場所で long を代わりに使用します。 特殊なジェネリック クラスにはそれぞれ、ネイティブで値型が含まれているため、変換は必要ありません。

参照型の場合、ジェネリックの動作は少し異なります。 何らかの参照型でジェネリック型を初めて構築するとき、ランタイムは、CIL のパラメーターの代わりに使用されているオブジェクト参照を利用して特殊なジェネリック型を作成します。 その後、構築された型が参照型をそのパラメーターとしてインスタンス化されるたびに、型に関係なく、ランタイムは、以前に利用した特殊なバージョンのジェネリック型を再利用します。 すべての参照のサイズが同じであるため、これが可能になります。

たとえば、Customer クラスと Order クラスという 2 つの参照型があるとき、Customer 型のスタックを作成したとします。

class Customer { }
class Order { }
Stack<Customer> customers;

この時点で、オブジェクト参照を格納する Stack<T> クラスの特殊なバージョンがランタイムにより生成されます。データを保存しなくても、後にオブジェクト参照にデータが入力されます。 次のコード行により、Order という名前のもう 1 つの参照型のスタックが作成されるとします。

Stack<Order> orders = new Stack<Order>();

値型とは異なり、Stack<T> クラスのもう 1 つの特殊なバージョンは Order 型に対して作成されません。 代わりに、Stack<T> クラスの特殊なバージョンのインスタンスが作成され、それを参照するように orders 変数が設定されます。 その後、Customer 型のスタックを作成するコード行が見つかったとします。

customers = new Stack<Customer>();

Order 型を利用して作成された Stack<T> クラスの前の使用と同様に、特殊な Stack<T> クラスのもう 1 つのインスタンスが作成されます。 そこに含まれるポインターは、Customer 型のサイズのメモリ領域を参照するように設定されています。 参照型はプログラムによって大きく異なることがあるため、ジェネリックの C# 実装では、コードの量が大幅に、参照型のジェネリック クラスのコンパイラにより作成された特殊なクラスの数まで減ります。

また、ジェネリック C# クラスが値型または参照型パラメーターの利用によりインスタンス化されるとき、ランタイム時にリフレクションがクエリを実行し、その型パラメーターを確かめることができます。

関連項目