C# ジェネリクスとリフレクション

リフレクションを使うと、実行時にジェネリック型の情報を調べたり、動的にジェネリック型を構築したりできます。高度な機能ですが、ライブラリやフレームワーク開発で役立ちます。

ジェネリック型かどうかを判定

Type.IsGenericType プロパティでジェネリック型かどうかを確認できます。

var listType = typeof(List<int>);
var stringType = typeof(string);

Console.WriteLine(listType.IsGenericType);  // True
Console.WriteLine(stringType.IsGenericType); // False

型パラメータを取得

GetGenericArguments() で、ジェネリック型に渡された型引数を取得できます。

var dictType = typeof(Dictionary<string, int>);

Type[] typeArgs = dictType.GetGenericArguments();
foreach (var arg in typeArgs)
{
    Console.WriteLine(arg.Name);
}
// String
// Int32

ジェネリック型定義の取得

GetGenericTypeDefinition() で、型パラメータが埋まっていない「型定義」を取得できます。

var closedType = typeof(List<int>);
var openType = closedType.GetGenericTypeDefinition();

Console.WriteLine(closedType.Name); // List`1
Console.WriteLine(openType.Name);   // List`1
Console.WriteLine(openType == typeof(List<>)); // True

List<> のように型引数を指定しない形が「オープンジェネリック型」です。

動的にジェネリック型を構築

MakeGenericType() を使うと、実行時に型引数を指定してジェネリック型を作れます。

Type openType = typeof(List&lt;&gt;);
Type closedType = openType.MakeGenericType(typeof(string));

object instance = Activator.CreateInstance(closedType)!;
Console.WriteLine(instance.GetType()); // System.Collections.Generic.List`1[System.String]

これは依存性注入コンテナなどで活用されるテクニックです。

ジェネリックメソッドの呼び出し

リフレクションでジェネリックメソッドを呼び出すには、まず MakeGenericMethod() で型引数を指定します。

public class Utility
{
    public static T CreateDefault&lt;T&gt;() where T : new()
    {
        return new T();
    }
}

// リフレクションで呼び出し
var method = typeof(Utility).GetMethod("CreateDefault")!;
var genericMethod = method.MakeGenericMethod(typeof(StringBuilder));
object? result = genericMethod.Invoke(null, null);

Console.WriteLine(result?.GetType()); // System.Text.StringBuilder

制約の確認

GetGenericParameterConstraints() で、型パラメータに付けられた制約を調べられます。

public class Sample&lt;T&gt; where T : class, IDisposable, new() { }

var constraints = typeof(Sample&lt;&gt;)
    .GetGenericArguments()[0]
    .GetGenericParameterConstraints();

foreach (var c in constraints)
{
    Console.WriteLine(c.Name);
}
// IDisposable
コンパイル時のジェネリクス

型安全、高速、シンプル

リフレクションによるジェネリクス

動的、柔軟、オーバーヘッドあり

注意点

リフレクションは強力ですが、以下の点に注意が必要です。

パフォーマンス

リフレクションは通常のメソッド呼び出しより大幅に遅い

型安全性

コンパイル時の型チェックが効かないため、実行時エラーの可能性

AOT コンパイル

Ahead-of-Time コンパイル環境では制限を受けることがある

リフレクションとジェネリクスの組み合わせは、シリアライザや ORM、DI コンテナなどの基盤技術で多用されています。通常のアプリケーションコードでは使う機会は少ないですが、仕組みを理解しておくとライブラリの動作原理がわかるようになります。