次の方法で共有


式ツリーを作成する

C# コンパイラは、これまでに見てきたすべての式ツリーを作成しました。 あなたは、Expression<Func<T>>または同様の型に型指定された変数に割り当てるラムダ式を作成しました。 多くのシナリオでは、実行時にメモリ内に式を作成します。

式ツリーは変更できません。 不変であるということは、葉から根までツリーを構築する必要があることを意味します。 式ツリーの構築に使用する API は、この事実を反映しています。ノードの構築に使用するメソッドは、そのすべての子を引数として受け取ります。 いくつかの例を見て、その手法を紹介しましょう。

ノードの作成

最初に、これらのセクションを通じて取り組んできた加算式を使用して始めます。

Expression<Func<int>> sum = () => 1 + 2;

その式ツリーを構築するには、最初にリーフ ノードを構築します。 リーフ ノードは定数です。 Constant メソッドを使用してノードを作成します。

var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));

次に、加算式を作成します。

var addition = Expression.Add(one, two);

加算式を作成したら、ラムダ式を作成します。

var lambda = Expression.Lambda(addition);

このラムダ式には引数がありません。 このセクションの後半では、引数をパラメーターにマップし、より複雑な式を作成する方法について説明します。

このような式の場合、すべての呼び出しを 1 つのステートメントに結合できます。

var lambda2 = Expression.Lambda(
    Expression.Add(
        Expression.Constant(1, typeof(int)),
        Expression.Constant(2, typeof(int))
    )
);

ツリーを構築する

前のセクションでは、メモリ内に式ツリーを構築する方法の基本を示しました。 一般に、複雑なツリーは、より多くのノード タイプを意味し、ツリー内のノード数が多くなります。 もう 1 つの例を実行し、式ツリーを作成するときに通常構築する 2 つのノード型 (引数ノードとメソッド呼び出しノード) を示します。 式ツリーを作成して、この式を作成してみましょう。

Expression<Func<double, double, double>> distanceCalc =
    (x, y) => Math.Sqrt(x * x + y * y);

まず、 xyのパラメーター式を作成します。

var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");

乗算式と加算式の作成は、既に確認したパターンに従います。

var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);

次に、 Math.Sqrt呼び出しのメソッド呼び出し式を作成する必要があります。

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);

メソッドが見つからない場合、 GetMethod 呼び出しは null を返す可能性があります。 ほとんどの場合、これはメソッド名のスペルが間違っているためです。 それ以外の場合は、必要なアセンブリが読み込まれない可能性があります。 最後に、メソッド呼び出しをラムダ式に配置し、ラムダ式の引数を必ず定義します。

var distanceLambda = Expression.Lambda(
    distance,
    xParameter,
    yParameter);

このより複雑な例では、式ツリーを作成するために多くの場合に必要となるいくつかの手法が見られます。

最初に、パラメーターまたはローカル変数を表すオブジェクトを使用する前に作成する必要があります。 これらのオブジェクトを作成したら、必要に応じて式ツリーで使用できます。

次に、リフレクション API のサブセットを使用して System.Reflection.MethodInfo オブジェクトを作成し、そのメソッドにアクセスする式ツリーを作成できるようにする必要があります。 .NET Core プラットフォームで使用できるリフレクション API のサブセットに制限する必要があります。 繰り返しになりますが、こうした手法は他の式ツリーにも応用されます。

コードを詳細にビルドする

これらの API を使用して構築できる内容に制限はありません。 ただし、ビルドする式ツリーが複雑になるほど、コードの管理と読み取りがより困難になります。

次のコードと同等の式ツリーを構築してみましょう。

Func<int, int> factorialFunc = (n) =>
{
    var res = 1;
    while (n > 1)
    {
        res = res * n;
        n--;
    }
    return res;
};

上記のコードでは、式ツリーではなく、デリゲートのみが構築されました。 Expression クラスを使用すると、ステートメントラムダを作成することはできません。 同じ機能を構築するために必要なコードを次に示します。 while ループを構築するための API はありません。代わりに、条件付きテストを含むループと、ループから抜け出すラベル ターゲットを構築する必要があります。

var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");

// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));

var initializeResult = Expression.Assign(result, Expression.Constant(1));

// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
    Expression.Assign(result,
        Expression.Multiply(result, nArgument)),
    Expression.PostDecrementAssign(nArgument)
);

// Creating a method body.
BlockExpression body = Expression.Block(
    new[] { result },
    initializeResult,
    Expression.Loop(
        Expression.IfThenElse(
            Expression.GreaterThan(nArgument, Expression.Constant(1)),
            block,
            Expression.Break(label, result)
        ),
        label
    )
);

階乗関数の式ツリーを構築するコードは、かなり長く複雑になり、ラベルや改行ステートメント、および日常的なコーディングタスクで避けたいその他の要素で覆われています。

このセクションでは、この式ツリー内のすべてのノードにアクセスし、このサンプルで作成されたノードに関する情報を書き出すコードを記述しました。 サンプル コードは、dotnet/docs GitHub リポジトリで 表示またはダウンロード できます。 サンプルをビルドして実行して、自分で実験します。

コードコンストラクトを式にマップする

次のコード例は、API を使用して num => num < 5 ラムダ式を表す式ツリーを示しています。

// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
    Expression.Lambda<Func<int, bool>>(
        numLessThanFive,
        new ParameterExpression[] { numParam });

式ツリー API では、ループ、条件ブロック、 try-catch ブロックなどの割り当てと制御フロー式もサポートされています。 API を使用すると、C# コンパイラによってラムダ式から作成できる式ツリーよりも複雑な式ツリーを作成できます。 次の例では、数値の階乗を計算する式ツリーを作成する方法を示します。

// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.
BlockExpression block = Expression.Block(
    // Adding a local variable.
    new[] { result },
    // Assigning a constant to a local variable: result = 1
    Expression.Assign(result, Expression.Constant(1)),
        // Adding a loop.
        Expression.Loop(
           // Adding a conditional block into the loop.
           Expression.IfThenElse(
               // Condition: value > 1
               Expression.GreaterThan(value, Expression.Constant(1)),
               // If true: result *= value --
               Expression.MultiplyAssign(result,
                   Expression.PostDecrementAssign(value)),
               // If false, exit the loop and go to the label.
               Expression.Break(label, result)
           ),
       // Label to jump to.
       label
    )
);

// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);

Console.WriteLine(factorial);
// Prints 120.

詳細については、「 Visual Studio 2010 での式ツリーを使用した動的メソッドの生成」を参照してください。これは、Visual Studio の新しいバージョンにも適用されます。