ジェネリック型またはメソッドが共通中間言語 (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>();
ただし、Stack<T>やユーザー定義構造体などの異なる値型を持つ別のlong
クラスが、コード内の別の時点で作成されるとします。 その結果、ランタイムはジェネリック型の別のバージョンを生成し、CIL 内の適切な場所の long
に置き換えられます。 各特殊化ジェネリック クラスには値型がネイティブに含まれているため、変換は不要になります。
ジェネリックは参照型に対して多少異なる動作をします。 任意の参照型を使用してジェネリック型を初めて構築すると、ランタイムは、CIL のパラメーターに置き換わるオブジェクト参照を持つ特殊なジェネリック型を作成します。 その後、構築された型がどのような型でインスタンス化されるかにかかわらず、参照型としての型をパラメーターにするたびに、ランタイムは以前に作成された特殊化されたジェネリック型のバージョンを再利用します。 これは、すべての参照が同じサイズであるために可能です。
たとえば、 Customer
クラスと Order
クラスの 2 つの参照型があり、 Customer
型のスタックを作成したとします。
class Customer { }
class Order { }
Stack<Customer> customers;
この時点で、ランタイムは、データを格納する代わりに後で入力されるオブジェクト参照を格納する特殊なバージョンの Stack<T> クラスを生成します。 次のコード行で、 Order
という名前の別の参照型のスタックを作成するとします。
Stack<Order> orders = new Stack<Order>();
値型とは異なり、 Stack<T> クラスの別の特殊なバージョンは、 Order
型に対して作成されません。 代わりに、 Stack<T> クラスの特殊なバージョンのインスタンスが作成され、それを参照するように orders
変数が設定されます。 その後、 Customer
型のスタックを作成するコード行が発生したとします。
customers = new Stack<Customer>();
Stack<T>型を使用して作成されたOrder
クラスの以前の使用と同様に、特殊化されたStack<T> クラスの別のインスタンスが作成されます。 そこに含まれるポインターは、 Customer
型のサイズのメモリ領域を参照するように設定されます。 参照型の数はプログラムによって大きく異なる可能性があるため、ジェネリックの C# 実装では、参照型のジェネリック クラスに対してコンパイラによって作成された特殊なクラスの数を 1 つに減らすことで、コードの量が大幅に削減されます。
さらに、値型または参照型パラメーターを使用してジェネリック C# クラスをインスタンス化すると、リフレクションは実行時にクエリを実行でき、実際の型とその型パラメーターの両方を確認できます。
こちらも参照ください
.NET