(C#)Expression Tree(式木)を使って動的にインスタンスを生成する。

公開:2021/02/21
更新:2021/02/21
2 min読了の目安(約2300字TECH技術記事

Expression Treeとは

Expression Tree(式木)とは、式(数式)を木構造で表したもの。
例えば、X + Y は以下のようになります。

Expression Treeで表されるコードはコンパイルおよび実行可能であり
実行可能なコードの動的な変更や動的クエリの作成が可能になります。

ラムダ式からのExpression Tree作成

ラムダ式がExpression型の変数に割り当てられていると、匿名デリゲートではなく、式木としてコンパイルされます。
例えば、以下二つのコードは同じ意味になります。

Expression<Func<int, int>> exp = x => x + 5; 
// 引数x の式
var arg = Expression.Parameter(typeof(int), "x");
// x + 5の式
var body = Expression.Add(x, Expression.Constant(5));
// x => x + 5の式
var exp = Expression.Lambda<Func<int, int>>(body, x);

Expression.Addメソッドは2項演算の加算(+)です。
それぞれの演算子に対応したメソッドが用意されています。

Expression Treeを使ったインスタンス生成

Expression Treeを使うとインスタンスの動的生成も可能です。
以下のInstanceCreatorクラスでは式木を組み立てたあとコンパイルしてデリゲートにして返しています。

public static class InstanceCreator<T>
{
    /// <summary>
    /// T型のインスタンスを生成します
    /// </summary>
    public static Func<T> Create => CreateInstance();
    public static Func<T> CreateInstance()
    {
        return Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
    }
}

x => x + 5の例を参考にすると、
body(x + 5)にあたる部分がExpression.New(typeof(T))
Expression.Newメソッドは指定した型のコンストラクタを作成するメソッドです。
以下のように使用してインスタンスを生成できます。

public class Person { }
public class Test
{
  public void InstanceCreateTest()
  {
      var person = new Person();
      var expressionPerson = InstanceCreator<Person>.Create();
  }
}

new Person()とInstanceCreater<Person>.Create()はともにPersonクラスのインスタンスを生成します。

引数が必要な場合のインスタンス生成

public static class InstanceCreator<TParam, TInstance>
{
    /// <summary>
    /// 一つの引数を受け取ってTInstance型のインスタンスを生成します
    /// </summary>
    public static Func<TParam, TInstance> Create => CreateInstance();
    public static Func<TParam, TInstance> CreateInstance()
    {
        var argsType = new[] { typeof(TParam) };
        var constructor = typeof(TInstance).GetConstructor(argsType);
        var args = argsType.Select(Expression.Parameter).ToList();
        return Expression.Lambda<Func<TParam, TInstance>>(Expression.New(constructor, args), args).Compile();
    }
}

どういうときに使うのか

ジェネリクスを使った場合の動的インスタンス生成などに活用できます。
new()制約を使えばジェネリクスを使った場合でもインスタンスは生成できるのですが、遅い。
Expression Treeを使えば、new() T よりも速くインスタンス生成ができます。

参考

式ツリー