次の方法で共有


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

式ツリーは、コードを定義するデータ構造です。 式ツリーは、コンパイラがコードの分析とコンパイル済み出力の生成に使用するのと同じ構造に基づいています。 この記事を読むにつれて、式ツリーと、 アナライザーと CodeFixes をビルドするために Roslyn API で使用される型との間に、かなり類似していることがわかります。 (アナライザーと CodeFixes は、コードに対して静的分析を実行し、開発者に潜在的な修正プログラムを提案する NuGet パッケージです)。概念は似ていますが、最終的な結果は、意味のある方法でソース コードを調べることが可能なデータ構造になります。 ただし、式ツリーは、Roslyn API とは異なるクラスと API のセットに基づいています。 コードの行を次に示します。

var sum = 1 + 2;

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

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

視覚的には、ステートメント全体がツリーです。ルート ノードから開始し、ツリー内の各ノードに移動して、ステートメントを構成するコードを確認できます。

  • 代入を使用した変数宣言ステートメント (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);

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

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

式ツリーの構造には高い一貫性があります。 基本を学習したら、式ツリーとして表される最も複雑なコードも理解できます。 データ構造の優雅さは、C# コンパイラが最も複雑な C# プログラムを分析し、その複雑なソース コードから適切な出力を作成する方法を説明します。

式ツリーの構造に慣れると、その身につけた知識を他の高度なシナリオにもすぐに応用できるようになります。 表現ツリーには信じられないほどの力があります。

式ツリーでは、他の環境で実行するアルゴリズムを翻訳するだけでなく、コードを検査するアルゴリズムを実行する前に簡単に記述できます。 引数が式であるメソッドを記述し、コードを実行する前にそれらの式を調べます。 式ツリーは、コードの完全な表現です。すべての部分式の値が表示されます。 メソッド名とプロパティ名が表示されます。 定数式の値が表示されます。 式ツリーを実行可能なデリゲートに変換し、コードを実行します。

式ツリーの API を使用すると、ほぼすべての有効なコードコンストラクトを表すツリーを作成できます。 ただし、できるだけシンプルにするために、一部の C# イディオムを式ツリーに作成することはできません。 1 つの例として、( async キーワードと await キーワードを使用する) 非同期式があります。 非同期アルゴリズムが必要な場合は、コンパイラのサポートに依存するのではなく、 Task オブジェクトを直接操作する必要があります。 もう 1 つはループの作成です。 通常、これらのループは、 forforeachwhile 、または do ループを使用して作成します。 このシリーズの後半で説明するように、式ツリーの API は、ループの繰り返しを制御するbreak式とcontinue式を使用して、単一のループ式をサポートします。

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