式ツリー - コードを定義するデータ

式ツリーはコードを定義するデータ構造です。 式ツリーは、コンパイラがコードの分析とコンパイル済み出力の生成に使用するのと同じ構造に基づいています。 この記事を読むと、式ツリーと、Analyzers と CodeFixes を構築するために Roslyn API で使われる型の間に、多くの類似点があることがわかります。 (Analyzers と CodeFixes は、コードの静的な分析を実行して考えられる修正を開発者に提案する NuGet パッケージです)。概念は似ており、最終的な結果は、意味のある方法でソース コードを調べることができるデータ構造です。 ただし、式ツリーは Roslyn API とは異なるクラスと API のセットに基づいています。 次のコードがあります。

var sum = 1 + 2;

上のコードを式ツリーとして分析すると、ツリーにはいくつかのノードが含まれています。 最も外側のノードは、代入 (var sum = 1 + 2;) ありの変数宣言ステートメントです。この最も外側のノードには、いくつかの子ノードが含まれています。変数の宣言、代入演算子、そして等号の右側を表す式です。 この式は、さらに加算演算と加算の左右のオペランドを表す式に分割されます。

等号の右側を構成する式を詳しく見てみましょう。 式は、1 + 2 という二項式です。 具体的には、二項加算式です。 二項加算式には、加算式の左ノードと右ノードを表す 2 つの子があります。 ここでは、両方のノードは定数式です。左オペランドは値 1、右オペランドは値 2 です。

見た目では、ステートメント全体が 1 つのツリーです。ルート ノードから始めて、ツリーの各ノードをたどり、ステートメントを構成するコードを確認することができます。

  • 代入 (var sum = 1 + 2;) ありの変数宣言ステートメント
    • 暗黙的な変数の型宣言 (var sum)
      • 暗黙的な var キーワード (var)
      • 変数名の宣言 (sum)
    • 代入演算子 (=)
    • 二項加算式 (1 + 2)
      • 左オペランド (1)
      • 加算演算子 (+)
      • 右オペランド (2)

上のツリーは複雑に見えるかもしれませんが、とても強力です。 はるかに複雑な式を分解するときも、同じプロセスに従います。 次の式について考えます。

var finalAnswer = this.SecretSauceFunction(
    currentState.createInterimResult(), currentState.createSecondValue(1, 2),
    decisionServer.considerFinalOptions("hello")) +
    MoreSecretSauce('A', DateTime.Now, true);

上の式は、代入を含む変数宣言でもあります。 この例では、代入の右側はとても複雑なツリーです。 ここではこの式を分解しませんが、どのようなノードがあるかを考えてみてください。 現在のオブジェクトをレシーバーとして使用するメソッド呼び出しがあります。明示的な this レシーバーを持つものと、持たないものです。 他のレシーバー オブジェクトを使用するメソッド呼び出しがあり、さまざまな型の定数の引数があります。 最後に、二項加算演算子があります。 SecretSauceFunction() または MoreSecretSauce() の戻り値の型にもよりますが、二項加算演算子がオーバーライドされた加算演算子のメソッド呼び出しになり、静的メソッド呼び出しがクラスに定義されている二項加算演算子に解決されることがあります。

このような複雑さはありますが、上の式によって作成されるツリー構造は、最初のサンプルと同じくらい簡単にナビゲートできます。 子ノードを走査して、式の中のリーフ ノードを見つけます。 親ノードには子への参照があり、各ノードにはノードの種類を記述するプロパティがあります。

式ツリーの構造には高い一貫性があります。 基本がわかっていると、非常に複雑なコードが式ツリーとして表された場合でも理解できます。 データ構造の正確さにより、C# コンパイラが非常に複雑な C# プログラムを分析し、その複雑なソース コードから適切な出力を生成する方法がわかります。

式ツリーの構造に慣れると、その身につけた知識を他の高度なシナリオにもすぐに応用できるようになります。 式ツリーには優れた機能があります。

他の環境で実行されるアルゴリズムを変換するだけでなく、式ツリーを使うとコードを実行前に調べるアルゴリズムの作成が簡単になります。 引数が式であるメソッドを作成し、コードを実行する前にその式を調べます。 式ツリーは、コード全体を表したものです。任意のサブ式の値を確認できます。 メソッド名とプロパティ名が表示されます。 定数式の値が表示されます。 式ツリーを実行可能なデリゲートに変換して、コードを実行します。

式ツリーの API を使用すると、ほぼすべての有効なコード コンストラクトを表すツリーを作成できます。 ただし、可能な限り単純にするために、式ツリーでは一部の C# の表現方法を作成できません。 たとえば、(async および await キーワードを使用する) 非同期式です。 非同期アルゴリズムが必要な場合は、コンパイラのサポートに頼らず、Task オブジェクトを直接操作する必要があります。 もう 1 つの例は、ループの作成時です。 通常、これらのループを作成するには、forforeachwhile、または do ループを使います。 このシリーズの後半で見るように、式ツリーの API では、ループの繰り返しを制御する break および continue 式を含む、1 つのループ式がサポートされています。

実行できないことの 1 つが式ツリーの変更です。 式ツリーは不変のデータ構造です。 式ツリーを変更するには、元の式ツリーのコピーに変更を加えた新しいツリーを作成する必要があります。