.NET ランタイムによる式ツリーのサポート

.NET ランタイムには、式ツリーを使用するクラスが多くあります。 クラスの全リストは System.Linq.Expressions で確認できます。 ここでは、そのすべてを列挙するのではなく、ランタイム クラスがどのように設計されているのかを説明します。

言語設計の観点から言えば、式は、評価して値を返すコードの本体です。 式は単純な場合があります。定数式 1 は定数値 1 を返します。 式が複雑になる場合もあります。式 (-B + Math.Sqrt(B*B - 4 * A * C)) / (2 * A) は二次方程式の 1 つの解を返します (式に解がある場合)。

System.Linq.Expression と派生型

式ツリーの使用が複雑になる理由の 1 つは、プログラムの多くの場所でさまざまな種類の式が有効になることです。 代入式を考えてみます。 代入式の右側には、定数値、変数、メソッドの呼び出し式、その他を含めることができます。 言語に柔軟性があるため、式ツリーをたどっていくと、ツリーのノードのあらゆる場所でさまざまな式の型が使用されていることに気づくはずです。 そのため、基本の式の型を使用できるときは、それが最も簡単な操作方法です。 しかし、場合によっては、それ以上の知識が必要です。 このために、基本の式クラスに NodeType プロパティが含まれています。 それによって返される ExpressionType は、可能な式の型の列挙型です。 ノードの型がわかったら、その型にキャストし、式ノードの型を識別する特定のアクションを実行します。 特定の型のノードを検索し、その種類の式が持つ特定のプロパティを使用できます。

たとえば、次のコードでは変数アクセス式の変数名を出力します。 次のコードでは、ノードの型を調べてから、変数アクセス式にキャストした後、特定の式の型のプロパティを調べる手順を示します。

Expression<Func<int, int>> addFive = (num) => num + 5;

if (addFive is LambdaExpression lambdaExp)
{
    var parameter = lambdaExp.Parameters[0];  -- first

    Console.WriteLine(parameter.Name);
    Console.WriteLine(parameter.Type);
}

式ツリーを作成する

System.Linq.Expression クラスには、式を作成する静的メソッドも数多く含まれています。 これらのメソッドは、その子に指定された引数を使用して式ノードを作成します。 このようにして、リーフ ノードから式を作成します。 たとえば、次のコードは Add 式を作成します。

// Addition is an add expression for "1 + 2"
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);

この簡単な例からわかるように、さまざまな型が式ツリーの作成と操作に関わっています。 C# 言語が備える豊富な語彙が提供する機能を発揮するには、こうした複雑さが必要になります。

ほぼすべての C# 言語の構文要素に対してマップされる式ノード型がそれぞれ存在します。 各型には、その型の言語要素に特有のメソッドがあります。 一度に覚えておくべきことがたくさんあります。 式ツリーを操作するときは、すべてを憶えておくのではなく、次に示す手法を使います。

  1. ExpressionType 列挙型のメンバーを調べて、検証するノードを特定します。 このリストは、式ツリーを走査して理解するときに役立ちます。
  2. Expression クラスの静的メンバーを調べて、式を作成します。 これらのメソッドでは、その子ノードから任意の式の型を作成できます。
  3. ExpressionVisitor クラスを調べて、変更された式ツリーを作成します。

それら 3 つの部分を調べれば、さらに多くのことがわかります。 それら 3 つのステップのいずれかから始めると、必要なことが必ず見つかります。