3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Generic型のインターフェイスの型をダイナミックに定義する

Posted at

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> をダイナミックに指定したいだけなのだが、ノイズが多くなるのでシンプルなクラスを作る。単純にジェネリック型を使って、定義している。さて、このインターフェイスをどうやってダイナミックに取得するか?

IOptions.cs
    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[]) を使っているのはこんなコード

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) が本来の引数なので、必要な数のジェネリックパラメータを渡せばよい。

ネステッドクラス

ドキュメントに面白い例が載っていた。このような場合は、どうやって、メソッドを使えばよいだろう?

NestedClass.cs
    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のジェネリックインターフェイスを動的に指定したい場合に使えそう。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?