English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

LINQ 表現木

前節で式について学びました。ここで式木について学びましょう。

その名の通りのように、式木は単なる木構造で並べられた式です。式木の各ノードは一つの式です。例えば、式木は数学の公式x < yを表すことができます。ここでx、<、yは式として表され、木構造に並べられます。

式木はlambda式のメモリ表現形式です。式木はクエリの実際の要素を保存し、クエリの結果を保存しません。

式木はlambda式の構造を透明で明確にします。式木内のデータと他のデータ構造と同様にデータを交渉できます。

例えば、以下のisTeenAgerExpr式を見てみましょう:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

コンパイラは上記の式を以下の式木に変換します:

例:C#の表現式ツリー

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

あなたは手動で表現木も構築できます。以下のシンプルなlambda表現の表現木の構築方法を見てみましょう:

例:C#のFuncデリゲート:

Func<Student, bool> isAdult = s => s.age >= 18;

このFunc型デリゲートは以下のメソッドとして見なされます:

 C#:

public bool function(Student s)
{
  return s.Age > 18;
}

表現木を作成するためにまず、以下のようにパラメータ表現を作成します。Studentはパラメータのタイプ、's'はパラメータの名前です:

手順1C#でパラメータ表現を作成する

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

今、Expression.Property()を使用してs.Age表現を作成します。sはパラメータ、AgeはStudentの属性名です。(Expressionは抽象クラスであり、手動で表現木を作成するための静的ヘルパーメソッドを含んでいます。)

手順2C#で属性表現を作成する

MemberExpression me = Expression.Property(pe, "Age");

今、18定数表現を作成します:

手順3C#で定数表現を作成する

ConstantExpression constant = Expression.Constant(18, typeof(int));

今までに、s.Age(メンバー表現)と18(定数表現)で表現木を構築しました。今や、メンバー表現が定数表現よりも大きいかどうかを確認する必要があります。そのためには、Expression.GreaterThanOrEqual()メソッドを使用し、メンバー表現と定数表現をパラメータとして渡します::

手順4C#で二進数表現を作成する

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

したがって、lambda表現の主体 s.Age>を設定します 18 表現木を構築しました。今や、パラメータ表現と主体表現を結合する必要があります。Expression.Lambdaを使用します(body, parameters array)でlambda表現s => s.age>を結合する 18のbody(主体)とparameter(パラメータ)部分:

手順5C#でLambda表現を作成する

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

このようにして、lambda表現を持つ簡単なFuncデリゲートで表現木を構築できます。

例:C#の表現式ツリー

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
MemberExpression me = Expression.Property(pe, "Age");
ConstantExpression constant = Expression.Constant(18, typeof(int));
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
Console.WriteLine("表現木: {0}", ExpressionTree);
        
Console.WriteLine("表現木の体: {0}", ExpressionTree.Body);
        
Console.WriteLine("Expression Treeのボディ: {0}", ExpressionTree.Body) 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("表現木のパラメータ: {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")
Dim mexp As MemberExpression = Expression.Property(pe, "Age")
Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))
Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)
Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) =
Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() {pe})
Console.WriteLine("Expression Tree: {0}", ExpressionTree)
        
Console.WriteLine("Expression Treeのボディ: {0}", ExpressionTree.Body) 
                                Console.WriteLine("Expression Treeのパラメータの数: {0}",
        
Console.WriteLine("Expression Treeのパラメータ: {0}", ExpressionTree.Parameters(0))
出力:}}
Expression Tree: s => s.Age >= 18)
Expression Treeのボディ: s.Age >= 18)
Expression Treeのパラメータの数: 1
Expression Treeのパラメータ: s

以下の図は、Expression Treeの作成プロセス全体を示しています:

Expression Treeの構築

なぜExpression Treeを選んだのか?

前節で、ラムダ式に割り当てられたものを見てきました:Func<T>コンパイルされた実行可能コードは、ラムダ式に割り当てられます。Expression<TDelegate>タイプはExpression树としてコンパイルされます。

実行可能コードは同じアプリケーションドメイン内で実行され、メモリ内の集合を処理します。可枚挙の静的クラスは、以下を実現するために含まれていますIEnumerable<T>インターフェースのメモリ内の集合の拡張メソッド、例えばList<T>、Dictionary<T>など。Enumerableクラスの拡張メソッドはFuncタイプのデリゲートのポリシー引数を受け取ります。例えば、Where拡張メソッドはFunc<TSource, bool>のポリシーその後、同一AppDomain内のメモリ内の集合を処理するためにIL(中間言語)にコンパイルされます。

以下の図は、EnumerableクラスのWhere拡張メソッドがFuncデリゲートをパラメータとして含む場合を示しています:

WhereのFuncデリゲート

Funcデリゲートは原始の実行可能コードであり、そのため、デバッグコードを行うと、以下が見つかります:Funcデリゲートは不透明なコードとして表されます。そのパラメータ、返り値の型、主体を見ることができません:

デバッグモードのFuncデリゲート

Funcデリゲートはメモリ内のコレクションに使用されます、なぜならそれは同じAppDomainで処理されるからです、しかしLINQ-to-SQL、EntityFramework、またはLINQ機能を提供する他のサードパーティ製品のリモートLINQクエリプロバイダーはどうですか?彼らは、ラムダ式がオリジナルの実行可能コードにコンパイルされた場合、パラメータ、ラムダ式の返却型式、および実行時クエリの構築をさらに処理するためにどのように解析しますか?答えは表現ツリー.

Expression <TDelegate>は、表現ツリーと呼ばれるデータ構造にコンパイルされます。

デバッグコードを実行すると、表現は以下のように表示されます:

デバッグモードの表現式ツリー

今あなたは通常のデリゲートと表現の違いを見ることができます。表現式ツリーは透明です。以下のように、パラメータ、返却型式、本体表現の情報を表現から取得できます:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expression: {0}", isTeenAgerExpr);
        
Console.WriteLine("表現式型式: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("引数名前: {0}", param.Name);
    Console.WriteLine("引数型式: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("表現式本体左側: {0}", bodyExpr.Left);
Console.WriteLine("二進数表現式型式: {0}", bodyExpr.NodeType);
Console.WriteLine("表現式本体右側: {0}", bodyExpr.Right);
Console.WriteLine("返却型式: {0}", isTeenAgerExpr.ReturnType);
出力:}}
Expression: s => ((s.Age > 12) AndAlso (s.Age < 20))
表現タイプ: Lambda
パラメータ名: s
パラメータタイプ: Student
表現本体の左側: (s.Age > 12)
バイナリ表現タイプ: AndAlso
表現本体の右側: (s.Age < 20)
返却型: System.Boolean

LINQに対して同じアプリケーションドメイン内で実行されない-to-SQLまたはEntity FrameworkのLINQ検索。例えば、Entity Frameworkの以下のLINQ検索はプログラム内部で実際には実行されません:

例:C#でのLINQ検索
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

まずSQL文に変換し、その後データベースサーバー上で実行します。

クエリ表現で見つかったコードは、文字列として他のプロセスに送信されるSQLクエリに変換する必要があります。LINQの場合、-to-SQLまたはEntity Framework、このプロセスはSQL Serverデータベースです。データ構造(例えば、表現木)をSQLに変換することは、元のILや実行可能コードをSQLに変換するよりもはるかに簡単です。なぜなら、ご覧の通り、表現から情報を抽出することは簡単だからです。

表現木を作成する目的は、クエリ表現などのコードを他のプロセスに渡してここで実行できる文字列に変換することです。

可検索の静的クラスには、Expression型のパラメータを受け入れる拡張メソッドがあります。このパラメータ式を表現木に変換し、それをデータ構造としてリモートLINQプロバイダーに渡して、プロバイダーが適切なクエリを構築してクエリを実行できるようにします。