C# 関数型プログラミングの詳細 (7) 式ツリー:データとしての関数

[C# シリーズ経由の LINQ]

[C# 関数型プログラミングの詳細シリーズ]

最新バージョン:https://weblogs.asp.net/dixin/functional-csharp-function-as-data-and-expression-tree

C# ラムダ式は、強力なシンタックス シュガーです。無名関数を表すだけでなく、同じ構文で式ツリーも表すことができます。

式ツリーとしてのラムダ式

式ツリーは、匿名関数と同じラムダ式構文で作成できます:

internal static partial class ExpressionTree
{
    internal static void ExpressionLambda()
    {
        // Func<int, bool> isPositive = int32 => int32 > 0;
        Expression<Func<int, bool>> isPositiveExpression = int32 => int32 > 0;
    }
}

今回は、ラムダ式の予想される型は Func 関数型ではなく、Expression> になります。ここでのラムダ式は、実行可能な無名関数にコンパイルされるのではなく、式ツリーと呼ばれるその関数のロジックを表すツリー データ構造になります。

メタプログラミング:データとして機能

上記のラムダ式は、式ツリー構築コードにコンパイルされます:

internal static void CompiledExpressionLambda()
{
    ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "int32"); // int32 parameter.
    ConstantExpression constantExpression = Expression.Constant(0, typeof(int)); // 0
    BinaryExpression greaterThanExpression = Expression.GreaterThan(
        left: parameterExpression, right: constantExpression); // int32 > 0

    Expression<Func<int, bool>> isPositiveExpression = Expression.Lambda<Func<int, bool>>(
        body: greaterThanExpression, // ... => int32 > 0
        parameters: parameterExpression); // int32 => ...
}

ここで、Expression> インスタンスはツリー全体を表し、ParameterExpression、ConstantExpression、BinaryExpression インスタンスはそのツリーのノードです。そして、それらはすべて System.Linq.Expressions.Expression タイプから派生しています:

namespace System.Linq.Expressions
{
    public abstract partial class Expression
    {
        public virtual ExpressionType NodeType { get; }

        public virtual Type Type { get; }

        // Other members.
    }

    public class ParameterExpression : Expression
    {
        public string Name { get; }

        // Other members.
    }

    public class ConstantExpression : Expression
    {
        public object Value { get; }

        // Other members.
    }

    public class BinaryExpression : Expression
    {
        public Expression Left { get; }

        public Expression Right { get; }

        // Other members.
    }

    public abstract class LambdaExpression : Expression
    {
        public Expression Body { get; }

        public ReadOnlyCollection<ParameterExpression> Parameters { get; }

        // Other members.
    }

    public sealed class Expression<TDelegate> : LambdaExpression
    {
        public TDelegate Compile();

        // Other members.
    }
}

上記の式ツリーのデータ構造は、次のように視覚化できます:

Expression<Func<int, bool>> (NodeType = Lambda, Type = Func<int, bool>)
|_Parameters
| |_ParameterExpression (NodeType = Parameter, Type = int)
|   |_Name = "int32"
|_Body
  |_BinaryExpression (NodeType = GreaterThan, Type = bool)
    |_Left
    | |_ParameterExpression (NodeType = Parameter, Type = int)
    |   |_Name = "int32"
    |_Right
      |_ConstantExpression (NodeType = Constant, Type = int)
        |_Value = 0

したがって、この式ツリーは抽象構文ツリーであり、C# 関数のソース コード int32 => int32> 0 の抽象構文構造を表しています。各ノードには NodeType プロパティと Type プロパティがあることに注意してください。 NodeType はツリーで表された構成型を返し、Type は表された .NET 型を返します。たとえば、上記の ParameterExpression は、ソース コードで int パラメーターを表すパラメーター ノードであるため、その NodeType は Parameter であり、その Type は int です。

要約すると、

Func<int, bool> isPositive = int32 => int32 > 0; // Code.

そして

Expression<Func<int, bool>> isPositiveExpression = int32 => int32 > 0; // Data.

  • isPositive 変数は、デリゲート インスタンスによって表される関数であり、呼び出すことができます。ラムダ式 int32 => int32> 0 は実行可能コードにコンパイルされます。 isPositive が呼び出されると、このコードが実行されます。
  • isPositiveExpression 変数は、抽象的な構文ツリー データ構造です。したがって、実行可能関数のように直接呼び出すことはできないようです。ラムダ式 int32 => int32> 0 は、各ノードが Expression インスタンスである式ツリーの構築にコンパイルされます。このツリー全体は、関数 int32 => int32> 0 の構文構造とロジックを表します。これはラムダ式であるため、このツリーの最上位ノードは Expression> インスタンスです。 2 つの子ノードがあります:
    • ラムダ式のすべてのパラメータを表す ParameterExpression コレクション。ラムダ式には 1 つのパラメーターがあるため、このコレクションには 1 つのノードが含まれます:
      • 「int32」という名前の int パラメータを表す ParameterExpression インスタンス。
    • BinaryExpression インスタンスであるラムダ式の本体を表す Body ノードは、2 つのオペランドの「>」(より大きい) 比較である本体を表します。したがって、2 つの子ノードがあります:
      • 左側のオペランドを表す、上記の ParameterExpression インスタンスの参照。
      • 右側のオペランド 0 を表す ConstantExpression インスタンス。

式ツリーの各ノードは、豊富な情報で厳密に型指定されているためです。ノードをトラバースして、表現された関数の C# ソース コード ロジックを取得し、別の言語のロジックに変換できます。ここで isPositiveExpression は、int 値が定数 0 より大きいかどうかを述語する関数ロジックを表し、SQL WHERE 句などで SQL クエリの大なり述語に変換できます。

.NET 式

上記の ParameterExpression、ConstantExpression、BinaryExpression、LambdaExpression に加えて、.NET は式ノードの豊富なコレクションを提供します。以下はその継承階層です:

  • 表情
    • BinaryExpression
    • ブロック式
    • 条件式
    • 定数式
    • DebugInfo式
    • デフォルト式
    • 動的表現
    • GotoExpression
    • インデックス式
    • 呼び出し式
    • ラベル式
    • ラムダ式
    • ListInitExpression
    • ループ式
    • メンバー式
    • MemberInitExpression
    • メソッド呼び出し式
    • NewArrayExpression
    • 新しい式
    • パラメータ式
    • ランタイム変数式
    • スイッチ式
    • TryExpression
    • TypeBinaryExpression
    • 単項式

そして、上で示したように、Expression タイプのファクトリ メソッドを呼び出すことで、式をインスタンス化できます。

public abstract partial class Expression
{
    public static ParameterExpression Parameter(Type type, string name);

    public static ConstantExpression Constant(object value, Type type);

    public static BinaryExpression GreaterThan(Expression left, Expression right);

    public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters);
}

式には、式のインスタンス化のすべてのケースをカバーする他の多くのファクトリ メソッドがあります:

public abstract partial class Expression
{
    public static BinaryExpression Add(Expression left, Expression right);

    public static BinaryExpression Subtract(Expression left, Expression right);

    public static BinaryExpression Multiply(Expression left, Expression right);

    public static BinaryExpression Divide(Expression left, Expression right);

    public static BinaryExpression Equal(Expression left, Expression right);

    public static UnaryExpression ArrayLength(Expression array);

    public static UnaryExpression Not(Expression expression);

    public static ConditionalExpression Condition(Expression test, Expression ifTrue, Expression ifFalse);

    public static NewExpression New(ConstructorInfo constructor, params Expression[] arguments);

    public static MethodCallExpression Call(MethodInfo method, params Expression[] arguments);

    public static BlockExpression Block(params Expression[] expressions);

    // Other members.
}

一部の式ノードは、可能な NodeType 値を複数持つことができます。例:

  • UnaryExpression は、演算子とオペランドを持つ単項演算を表します。その NodeType は、ArrayLength、Negate、Not、Convert、Decrement、Increment、Throw、UnaryPlus などです。
  • BinaryExpression は、演算子、左オペランド、および右オペランドを使用したバイナリ演算を表します。その NodeType は、Add、And、Assign、Divide、Equal、.GreaterThan、GreaterThanOrEqual、LessThan、LessThanOrEqual、Modulo、Multiply、NotEqual、または、累乗、減算など

これまでのところ、C# コンパイラは、ラムダ式のこの「データとしての関数」シンタックス シュガーのみを実装しており、ステートメント ラムダではまだ利用できません。次のコードはコンパイルできません:

internal static void StatementLambda()
{
    Expression<Func<int, bool>> isPositiveExpression = int32 =>
    {
        Console.WriteLine(int32);
        return int32 > 0;
    };
}

コンパイラ エラーが発生します。ステートメント本体を含むラムダ式を式ツリーに変換できません。上記の式ツリーは手動で構築する必要があります:

internal static void StatementLambda()
{
    ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "int32"); // int32 parameter.
    Expression<Func<int, bool>> isPositiveExpression = Expression.Lambda<Func<int, bool>>(
        body: Expression.Block( // ... => {
            // Console.WriteLine(int32);
            Expression.Call(new Action<int>(Console.WriteLine).Method, parameterExpression),
            // return int32 > 0;
            Expression.GreaterThan(parameterExpression, Expression.Constant(0, typeof(int)))), // }
        parameters: parameterExpression); // int32 => ...
}

式ツリーを CIL にコンパイル

式ツリーはデータ - 抽象的な構文ツリーです。 C# と LINQ では、式ツリーは通常、関数の抽象的な構文構造を表すために使用されるため、SQL クエリや URI クエリなどの他のドメイン固有言語にコンパイルできます。これを実証するには、単純な数学関数を取り上げます。例として、double パラメーターを受け入れ、4 つの基本的な 2 進算術計算 (加算、減算、乗算、除算) を実行します。

internal static void ArithmeticalExpression()
{
    Expression<Func<double, double, double, double, double, double>> expression =
        (a, b, c, d, e) => a + b - c * d / 2 + e * 3;
}

ツリー全体は次のように視覚化できます:

Expression<Func<double, double, double, double, double, double>> (NodeType = Lambda, Type = Func<double, double, double, double, double, double>)
|_Parameters
| |_ParameterExpression (NodeType = Parameter, Type = double)
| | |_Name = "a"
| |_ParameterExpression (NodeType = Parameter, Type = double)
| | |_Name = "b"
| |_ParameterExpression (NodeType = Parameter, Type = double)
| | |_Name = "c"
| |_ParameterExpression (NodeType = Parameter, Type = double)
| | |_Name = "d"
| |_ParameterExpression (NodeType = Parameter, Type = double)
|   |_Name = "e"
|_Body
  |_BinaryExpression (NodeType = Add, Type = double)
    |_Left
    | |_BinaryExpression (NodeType = Subtract, Type = double)
    |   |_Left
    |   | |_BinaryExpression (NodeType = Add, Type = double)
    |   |   |_Left
    |   |   | |_ParameterExpression (NodeType = Parameter, Type = double)
    |   |   |   |_Name = "a"
    |   |   |_Right
    |   |     |_ParameterExpression (NodeType = Parameter, Type = double)
    |   |       |_Name = "b"
    |   |_Right
    |     |_BinaryExpression (NodeType = Divide, Type = double)
    |       |_Left
    |       | |_BinaryExpression (NodeType = Multiply, Type = double)
    |       |   |_Left
    |       |   | |_ParameterExpression (NodeType = Parameter, Type = double)
    |       |   |   |_Name = "c"
    |       |   |_right
    |       |     |_ParameterExpression (NodeType = Parameter, Type = double)
    |       |       |_Name = "d"
    |       |_Right
    |         |_ConstantExpression (NodeType = Constant, Type = int)
    |           |_Value = 2
    |_Right
      |_BinaryExpression (NodeType = Multiply, Type = double)
        |_Left
        | |_ParameterExpression (NodeType = Parameter, Type = double)
        |   |_Name = "e"
        |_Right
          |_ConstantExpression (NodeType = Constant, Type = int)
            |_Value = 3

これは非常に単純な式ツリーです。

  • 各内部ノードは、バイナリ演算の加算、減算、乗算、または除算を表すバイナリ ノード (BinaryExpression インスタンス) です。
  • 各リーフ ノードは、パラメーター (ParameterExpression インスタンス) または定数 (ConstantExpression インスタンス) のいずれかです。

このツリーには全部で 6 種類のノードがあります:

  • add:BinaryExpression { NodeType =ExpressionType.Add }
  • subtract:BinaryExpression { NodeType =ExpressionType.Subtract }
  • multiply:BinaryExpression { NodeType =ExpressionType.Multiply }
  • divide:BinaryExpression { NodeType =ExpressionType.Divide}
  • constant:ParameterExpression { NodeType =ExpressionType.Constant }
  • パラメータ:ConstantExpression { NodeType =ExpressionType.Parameter }

トラバース式ツリー

このツリーを再帰的にトラバースするのはとても簡単です。次の基本型は、トラバースの基本ロジックを実装します:

internal abstract class BinaryArithmeticExpressionVisitor<TResult>
{
    internal virtual TResult VisitBody(LambdaExpression expression) => this.VisitNode(expression.Body, expression);

    protected TResult VisitNode(Expression node, LambdaExpression expression)
    {
        // Processes the 6 types of node.
        switch (node.NodeType)
        {
            case ExpressionType.Add:
                return this.VisitAdd((BinaryExpression)node, expression);

            case ExpressionType.Constant:
                return this.VisitConstant((ConstantExpression)node, expression);

            case ExpressionType.Divide:
                return this.VisitDivide((BinaryExpression)node, expression);

            case ExpressionType.Multiply:
                return this.VisitMultiply((BinaryExpression)node, expression);

            case ExpressionType.Parameter:
                return this.VisitParameter((ParameterExpression)node, expression);

            case ExpressionType.Subtract:
                return this.VisitSubtract((BinaryExpression)node, expression);

            default:
                throw new ArgumentOutOfRangeException(nameof(node));
        }
    }

    protected abstract TResult VisitAdd(BinaryExpression add, LambdaExpression expression);

    protected abstract TResult VisitConstant(ConstantExpression constant, LambdaExpression expression);

    protected abstract TResult VisitDivide(BinaryExpression divide, LambdaExpression expression);

    protected abstract TResult VisitMultiply(BinaryExpression multiply, LambdaExpression expression);

    protected abstract TResult VisitParameter(ParameterExpression parameter, LambdaExpression expression);

    protected abstract TResult VisitSubtract(BinaryExpression subtract, LambdaExpression expression);
}

VisitNode メソッドはノード タイプを検出し、6 種類のノードすべてに対して 6 つの抽象メソッドにディスパッチします。次のタイプは、これらの 6 つのメソッドを実装しています:

internal class PrefixVisitor : BinaryArithmeticExpressionVisitor<string>
{
    protected override string VisitAdd
        (BinaryExpression add, LambdaExpression expression) => this.VisitBinary(add, "add", expression);

    protected override string VisitConstant
        (ConstantExpression constant, LambdaExpression expression) => constant.Value.ToString();

    protected override string VisitDivide
        (BinaryExpression divide, LambdaExpression expression) => this.VisitBinary(divide, "div", expression);

    protected override string VisitMultiply
        (BinaryExpression multiply, LambdaExpression expression) =>
            this.VisitBinary(multiply, "mul", expression);

    protected override string VisitParameter
        (ParameterExpression parameter, LambdaExpression expression) => parameter.Name;

    protected override string VisitSubtract
        (BinaryExpression subtract, LambdaExpression expression) =>
            this.VisitBinary(subtract, "sub", expression);

    private string VisitBinary( // Recursion: operator(left, right)
        BinaryExpression binary, string @operator, LambdaExpression expression) =>
            $"{@operator}({this.VisitNode(binary.Left, expression)}, {this.VisitNode(binary.Right, expression)})";
}

二項ノードにアクセスすると、接頭辞スタイルの演算子 (左、右) で再帰的に出力されます。たとえば、中置式 a + b は add(a, b) に変換されます。これは、引数 a と b を指定して add 関数を呼び出していると見なすことができます。次のコードは、関数本体のロジックを接頭辞付きの関数呼び出しスタイルで出力します:

internal static partial class ExpressionTree
{
    internal static void Prefix()
    {
        Expression<Func<double, double, double, double, double, double>> infix =
            (a, b, c, d, e) => a + b - c * d / 2 + e * 3;
        PrefixVisitor prefixVisitor = new PrefixVisitor();
        string prefix = prefixVisitor.VisitBody(infix); // add(sub(add(a, b), div(mul(c, d), 2)), mul(e, 3))
    }
}

実際、.NET には組み込みの System.Linq.Expressions.ExpressionVisitor 型が用意されています。ここでは、デモンストレーションのみを目的として、トラバーサーを最初から実装しています。

実行時の CIL への式ツリー

出力が後置スタイル (a, b, add) の場合、次のように表示できます:a をスタックにロードし、b をスタックにロードし、スタックに 2 つの値を追加します。これが、スタック ベースの CIL 言語のしくみです。そのため、CIL 命令を出力する別のビジターを作成できます。 CIL 命令は、System.Reflection.Emit.OpCode 構造体で表すことができます。したがって、出力は命令と引数のペアのシーケンスであり、OpCode 値のタプルと double 値 (オペランド) または null (オペランドなし) で表されます。

internal class PostfixVisitor : BinaryArithmeticExpressionVisitor<List<(OpCode, double?)>>
{
    protected override List<(OpCode, double?)> VisitAdd(
        BinaryExpression add, LambdaExpression expression) => this.VisitBinary(add, OpCodes.Add, expression);

    protected override List<(OpCode, double?)> VisitConstant(
        ConstantExpression constant, LambdaExpression expression) =>
            new List<(OpCode, double?)>() { (OpCodes.Ldc_R8, (double?)constant.Value) };

    protected override List<(OpCode, double?)> VisitDivide(
        BinaryExpression divide, LambdaExpression expression) =>
            this.VisitBinary(divide, OpCodes.Div, expression);

    protected override List<(OpCode, double?)> VisitMultiply(
        BinaryExpression multiply, LambdaExpression expression) =>
            this.VisitBinary(multiply, OpCodes.Mul, expression);

    protected override List<(OpCode, double?)> VisitParameter(
        ParameterExpression parameter, LambdaExpression expression)
    {
        int index = expression.Parameters.IndexOf(parameter);
        return new List<(OpCode, double?)>() { (OpCodes.Ldarg_S, (double?)index) };
    }

    protected override List<(OpCode, double?)> VisitSubtract(
        BinaryExpression subtract, LambdaExpression expression) =>
            this.VisitBinary(subtract, OpCodes.Sub, expression);

    private List<(OpCode, double?)> VisitBinary( // Recursion: left, right, operator
        BinaryExpression binary, OpCode postfix, LambdaExpression expression)
    {
        List<(OpCode, double?)> cils = this.VisitNode(binary.Left, expression);
        cils.AddRange(this.VisitNode(binary.Right, expression));
        cils.Add((postfix, (double?)null));
        return cils;
    }
}

次のコードは、一連の CIL コードを出力します:

internal static void Cil()
{
    Expression<Func<double, double, double, double, double, double>> infix =
        (a, b, c, d, e) => a + b - c * d / 2 + e * 3;

    PostfixVisitor postfixVisitor = new PostfixVisitor();
    IEnumerable<(OpCode, double?)> postfix = postfixVisitor.VisitBody(infix);
    foreach ((OpCode Operator, double? Operand) code in postfix)
    {
        $"{code.Operator} {code.Operand}".WriteLine();
    }
    // ldarg.s 0
    // ldarg.s 1
    // add
    // ldarg.s 2
    // ldarg.s 3 
    // mul 
    // ldc.r8 2 
    // div 
    // sub 
    // ldarg.s 4 
    // ldc.r8 3 
    // mul 
    // add
}

したがって、この式ツリーで表される C# ロジックは、CIL 言語に正常にコンパイルされます。

実行時に機能する式ツリー

上記のコンパイル済み CIL コードは実行可能であるため、実行時に関数を作成し、CIL コードをその関数に出力できます。この種の関数は動的関数の呼び出しです。これは、コンパイル時に生成される静的アセンブリではなく、実行時に生成されるためです。

internal static class BinaryArithmeticCompiler
{
    internal static TDelegate Compile<TDelegate>(Expression<TDelegate> expression)
    {
        DynamicMethod dynamicFunction = new DynamicMethod(
            name: string.Empty,
            returnType: expression.ReturnType,
            parameterTypes: expression.Parameters.Select(parameter => parameter.Type).ToArray(),
            m: typeof(BinaryArithmeticCompiler).Module);
        EmitIL(dynamicFunction.GetILGenerator(), new PostfixVisitor().VisitBody(expression));
        return (TDelegate)(object)dynamicFunction.CreateDelegate(typeof(TDelegate));
    }

    private static void EmitIL(ILGenerator ilGenerator, IEnumerable<(OpCode, double?)> il)
    {
        foreach ((OpCode Operation, double? Operand) code in il)
        {
            if (code.Operand == null)
            {
                ilGenerator.Emit(code.Operation); // add, sub, mul, div
            }
            else if (code.Operation == OpCodes.Ldarg_S)
            {
                ilGenerator.Emit(code.Operation, (int)code.Operand); // ldarg.s (int)index
            }
            else
            {
                ilGenerator.Emit(code.Operation, code.Operand.Value); // ldc.r8 (double)constant
            }
        }
        ilGenerator.Emit(OpCodes.Ret); // Returns the result.
    }
}

次のコードは、その使用方法を示しています:

internal static void Compile()
{
    Expression<Func<double, double, double, double, double, double>> expression =
        (a, b, c, d, e) => a + b - c * d / 2 + e * 3;
    Func<double, double, double, double, double, double> function = 
        BinaryArithmeticCompiler.Compile(expression);
    double result = function(1, 2, 3, 4, 5); // 12
}

.NET は、この目的のために組み込み API、System.Linq.Expressions.Expression の Compile メソッドを提供します - 実行時に式ツリーを実行可能な関数にコンパイルします:

internal static void BuiltInCompile()
{
    Expression<Func<double, double, double, double, double, double>> infix =
        (a, b, c, d, e) => a + b - c * d / 2 + e * 3;
    Func<double, double, double, double, double, double> function = infix.Compile();
    double result = function(1, 2, 3, 4, 5); // 12
}

内部的に、Expression.Compile は System.Linq.Expressions.Compiler.LambdaCompile の API を呼び出します。これは CIL コンパイラ実装への完全な式ツリーです。

式ツリーと LINQ リモート クエリ

式ツリーは、LINQ リモート クエリで非常に重要です。特にラムダ式を使用すると、式ツリーを簡単に構築でき、C# 式ツリーのロジックを別のドメインまたは別の言語にコンパイル/変換/翻訳するのも簡単だからです。上記の例では、式ツリーは実行可能な CIL に変換されます。前述のように、リレーショナル データベースのように、ローカルおよびリモートの LINQ クエリがあります。次の例は、ローカル イン メモリ オブジェクトのローカル LINQ to Objects クエリと、リレーショナル データベースのリモート LINQ to Entities クエリです。

internal static partial class ExpressionTree
{
    internal static void LinqToObjects(IEnumerable<Product> source)
    {
        IEnumerable<Product> query = source.Where(product => product.ListPrice > 0M); // Define query.
        foreach (Product result in query) // Execute query.
        {
            result.Name.WriteLine();
        }
    }

    internal static void LinqToEntities(IQueryable<Product> source)
    {
        IQueryable<Product> query = source.Where(product => product.ListPrice > 0M); // Define query.
        foreach (Product result in query) // Execute query.
        {
            result.Name.WriteLine();
        }
    }
}

上記の LINQ to Objects クエリのデータ ソースは、現在の .NET アプリケーションのローカル メモリ内にある Product オブジェクトのシーケンスです。 LINQ to Entities クエリのデータ ソースは、リモート リレーショナル データベースの Product テーブルであり、現在のローカル メモリでは使用できません。 LINQ では、ローカル データ ソースとクエリは IEnumerable で表され、リモート データ ソースとクエリは IQueryable で表されます。これらには、異なる LINQ クエリ拡張メソッドがあります。上の表に例を示します:

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TSource> Where<TSource>(
            this IEnumerable<TSource> source, Func<TSource, bool> predicate);
    }

    public static class Queryable
    {
        public static IQueryable<TSource> Where<TSource>(
            this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
    }
}

その結果、Where クエリと述語ラムダ式は、ローカル LINQ クエリとリモート LINQ クエリで同じ構文を共有しますが、コンパイルはまったく異なります。ローカル クエリの述語は機能するようにコンパイルされ、リモート クエリの述語は式ツリーにコンパイルされます。

internal static partial class CompiledExpressionTree
{
    [CompilerGenerated]
    private static Func<Product, bool> cachedPredicate;

    [CompilerGenerated]
    private static bool Predicate(Product product) => product.ListPrice > 0M;

    public static void LinqToObjects(IEnumerable<Product> source)
    {
        Func<Product, bool> predicate = cachedPredicate ?? (cachedPredicate = Predicate);
        IEnumerable<Product> query = Enumerable.Where(source, predicate);
        foreach (Product result in query) // Execute query.
        {
            TraceExtensions.WriteLine(result.Name);
        }
    }
}

internal static partial class CompiledExpressionTree
{
    internal static void LinqToEntities(IQueryable<Product> source)
    {
        ParameterExpression productParameter = Expression.Parameter(typeof(Product), "product");
        Expression<Func<Product, bool>> predicateExpression = Expression.Lambda<Func<Product, bool>>(
            Expression.GreaterThan(
                Expression.Property(productParameter, nameof(Product.ListPrice)),
                Expression.Constant(0M, typeof(decimal))),
            productParameter);

        IQueryable<Product> query = Queryable.Where(source, predicateExpression); // Define query.
        foreach (Product result in query) // Execute query.
        {
            TraceExtensions.WriteLine(result.Name);
        }
    }
}

実行時にローカル クエリが実行されると、ソース シーケンス内の各ローカル値に対して無名関数が呼び出され、通常、リモート クエリはドメイン固有の言語に変換されてから、リモート データ ソースに送信されて実行されます。ここで、LINQ to Entities クエリでは、述語式ツリーが SQL クエリの述語に変換され、データベースに送信されて実行されます。式ツリーから SQL への変換については、LINQ to Entities の章で説明します。