.NET の generics と C++ の template
.NET の generics と C++ の template の大きな違いの 1 つに,generics 型を実行時の型ジェネレータに使用できる点が挙げられます*1.
型引数が与えられていない generics 型は,open type (開いた型) と呼ばれる特殊な型です.
例えば,次のような generics 型を考えてみましょう.
public sealed class X<T> { }
このとき,型引数が与えられていない,開いた X 型は,次のように取得することができます.
Type TypeX = typeof(X<>);
このようにして得られた Type オブジェクトは,Type.MakeGenericType メソッドを通して型ジェネレータとして利用できます.
Converter<Type, Type> makeType = delegate(Type T) { // T から X<T> を生成する return typeof(X<>).MakeGenericType(T); };
これを利用すれば,再帰的に型を定義できそうです.試しに以下のようなコードを実行してみたところ,100 段目で TypeLoadException が発生しました.
using System; using System.Text; using System.Reflection; public sealed class A { } public sealed class X<T> { } static class Program { static void Main(string[] args) { Type TypeX = typeof(X<>); Converter<Type, Type> makeType = delegate(Type T) { return TypeX.MakeGenericType(T); }; Type type = typeof(A); for(int count = 0; ;) { try { type = makeType(type); ++count; } catch { Console.WriteLine("Failed at {0}", count); break; } } } }
TypeLoadException に格納されているメッセージを見てみると,どうやらこういう実験を行う者が出てくることは予想されていたようです.
アセンブリ 'GenericsTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' からの型 'X`1' に対するジェネリックのインスタンス化は深すぎます。このジェネリック型は、帰納的に定義または使用されていますか?
仰る通りでございます.
*1:generics 型の実体化は全て実行時検証を受けるため,C++ の template のような静的な型の生成とは異なり,あらゆる generics 型の実体化は動的であると考えた方がよいのかもしれません.([http://www.ailight.jp/blog/kazuk/archive/2006/08/01/11849.aspx:title=菊池さんからのご指摘]を受けてちょっと表現変更) 現在の CLR の実装では,型が初めて使用されるときに実行時型情報が生成されます.このとき,要求される型に矛盾が存在したり,CLR の定める条件を満たさなかったりすると,実行時例外が発生します.これは generics 型についても当てはまり,C# などのコンパイラがエラーを出さなかった型(情報)であっても,実行時の型情報生成段階での検証や空きメモリなどの条件をパスしない場合,例外が発生し,計算が中断してしまうことになります.