DIを使っているプロジェクトで、Generic型のインターフェイスの型名をダイナミックに取得する必要があった。Type.MakeGenericType
がそれなのだが、簡単なサンプルを書いてみて理解してみることにした。
定義
Type.MakeGenericType(Type[]) Method
Substitutes the elements of an array of types for the type parameters of the current generic type definition and returns a Type object representing the resulting constructed type.
という説明なのだが、これを読んだだけではいまいち理解できなかった。具体的なサンプルを書いてみる。
サンプル
本来は、C# で使う IOption<T>
をダイナミックに指定したいだけなのだが、ノイズが多くなるのでシンプルなクラスを作る。単純にジェネリック型を使って、定義している。さて、このインターフェイスをどうやってダイナミックに取得するか?
public interface IOptions<T>
{
T GetValue();
}
public class Options<T> : IOptions<T>
{
private readonly T _value;
public Options(T value) {
_value = value;
}
public T GetValue()
{
return _value;
}
}
MakeGenericType(Type[])
を使っているのはこんなコード
// サービスコレクションの定義
var services = new ServiceCollection();
// サービスコレクションに、Options<T> のインスタンスを IOptions<string> インターフェイスとして登録
services.AddSingleton<IOptions<string>>(new Options<string>("hello"));
// IOption<T> の型に `MakeGenericType()`を使って、パラメータとして型を渡す。
Type interfaceType = typeof(IOptions<>).MakeGenericType(typeof(string));
// どのようなインターフェイスの型になっているかを見てみる
Console.WriteLine($"InterfaceName: {interfaceType.Name}<{interfaceType.GenericTypeArguments[0].Name}>");
// 取得したインターフェイスの型を渡してサービスの実体 Options<string>("hello") を取得する
var serviceProvider = services.BuildServiceProvider();
var option = serviceProvider.GetService(interfaceType);
// リフレクションでメソッドを実行
MethodInfo methodInfo = interfaceType.GetMethod("GetValue");
var result = methodInfo.Invoke(option, new object[] { });
Console.WriteLine(result);
InterfaceName: IOptions`1<String>
hello
ちなみに、MakeGenericType(Type[] types) が本来の引数なので、必要な数のジェネリックパラメータを渡せばよい。
ネステッドクラス
ドキュメントに面白い例が載っていた。このような場合は、どうやって、メソッドを使えばよいだろう?
class NestedClass<A>
{
public class Second<B>
{
public class Third<C> { }
}
}
公式のドキュメントに乗っていたが、これは IL では次のように解釈される様子。
.class public NestedClas<A> {
.class nested public Second<A, B> {
.class nested public Third<A, B, C> {
}
}
}
この例で、Third<C>
のインターフェイスを取得したければ、3つのパラメータを渡すとよい。
Type nestedType = typeof(NestedClass<>.Second<>.Third<>).MakeGenericType(new Type[] { typeof(string), typeof(int), typeof(byte[]) });
Console.WriteLine($"NetstedType: {nestedType.Name}<{nestedType.GenericTypeArguments[0].Name},{nestedType.GenericTypeArguments[1].Name},{nestedType.GenericTypeArguments[2].Name}>");
予想通り
NetstedType: Third`1<String,Int32,Byte[]>
まとめ
MakeGenericType(Type[])
を使うと、ジェネリックインターフェイスやクラスの型を動的に作成することができるので、ジェネリック型のリフレクション操作を行いたいときや、DIのジェネリックインターフェイスを動的に指定したい場合に使えそう。