Type オブジェクトを使ってジェネリックメソッドを呼び出す方法

下のように、ジェネリックなメソッドを、非ジェネリックなオーバーロードメソッドから呼び出したいときがたま〜にあります。

// ジェネリックなメソッド
public string Greet<T>(string name)
{
    return string.Format("{0} {1}", typeof(T).Name, name);
}

// 非ジェネリックなメソッド
public string Greet(Type type, string name)
{
    // type に対応した Greet<T> を呼び出したい!
    // ↓のコードは動かない
    return Greet<type>(name);
}

ジェネリックなメソッドを持つクラスが、.NET Frameworkサードパーティ製のライブラリのとき、この場面に遭遇することが多いですね。ソースコードに手を加えることが出来ないので。


もしかしたら同じ悩みを抱えているかもしれない人に朗報です。無理やりジェネリックメソッドを呼び出しているコードを発見したので、その方法を紹介します。

using System;
using System.Linq.Expressions;

namespace TypeGenericSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var g = new Greeting();
            var result = g.Greet(typeof(string), "test");
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }

    public class Greeting
    {
        public string Greet<T>(string name)
        {
            return string.Format("{0} {1}", typeof(T).Name, name);
        }

        public string Greet(Type type, string name)
        {
            // () => this.Greet<type>(name)
            // というラムダ式を作る
            var exp = Expression.Lambda<Func<string>>(
                Expression.Call(
                    Expression.Constant(this),  // this
                    "Greet",                    // Greet
                    new Type[] { type },        // <type>
                    Expression.Constant(name)   // name
                )
            );

            // コンパイルして実行
            return exp.Compile()();
        }
    }
}

ジェネリックメソッドを呼び出すラムダ式を式木で作り、コンパイルして実行しています。式木を毎回コンパイルするのは効率が悪いので、コンパイルしたものはキャッシュして再利用したほうがいいですね。


それにしても、System.Linq.Expressions.Expression を利用するなんて頭いいなぁ。