# ラムダ式で式ツリーを作成
以下は、ラムダによって作成される最も基本的な式ツリーです。
Expression<Func<int, bool>> lambda = num => num == 42;
「手で」式ツリーを作成するには、 Expression
を使用する必要があります クラス。
上記の式は次と同等です:
ParameterExpression parameter = Expression.Parameter(typeof(int), "num"); // num argument
ConstantExpression constant = Expression.Constant(42, typeof(int)); // 42 constant
BinaryExpression equality = Expression.Equals(parameter, constant); // equality of two expressions (num == 42)
Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(equality, parameter);
# API を使用して式ツリーを作成する
using System.Linq.Expressions;
// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });
# 式ツリーのコンパイル
// Define an expression tree, taking an integer, returning a bool.
Expression<Func<int, bool>> expr = num => num < 5;
// Call the Compile method on the expression tree to return a delegate that can be called.
Func<int, bool> result = expr.Compile();
// Invoke the delegate and write the result to the console.
Console.WriteLine(result(4)); // Prints true
// Prints True.
// You can also combine the compile step with the call/invoke step as below:
Console.WriteLine(expr.Compile()(4));
# 式ツリーの解析
using System.Linq.Expressions;
// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
param.Name, left.Name, operation.NodeType, right.Value);
// Decomposed expression: num => num LessThan 5
# 式ツリーの基本
式ツリーは、各ノードが式であるツリー状のデータ構造でコードを表します
式ツリーを使用すると、実行可能コードを動的に変更したり、さまざまなデータベースで LINQ クエリを実行したり、動的クエリを作成したりできます。式ツリーで表されるコードをコンパイルして実行できます。
これらは動的言語ランタイム (DLR) でも使用され、動的言語と .NET Framework 間の相互運用性を提供し、コンパイラ ライターが Microsoft 中間言語 (MSIL) の代わりに式ツリーを出力できるようにします。
式ツリーは
経由で作成できます <オール>ラムダ式からの式ツリー
ラムダ式が Expression 型 variable に割り当てられると、コンパイラはラムダ式を表す式ツリーを構築するコードを出力します。
次のコード例は、ラムダ式 num => num <5 を表す式ツリーを C# コンパイラで作成する方法を示しています。
Expression<Func<int, bool>> lambda = num => num < 5;
API を使用した式ツリー
式を使用して作成された式ツリーも クラス。このクラスには、特定の型の式ツリー ノードを作成する静的ファクトリ メソッドが含まれています。
以下は、いくつかの種類のツリー ノードです。
<オール>次のコード例は、API を使用してラムダ式 num => num <5 を表す式ツリーを作成する方法を示しています。
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 = Expression.Lambda<Func<int, bool>>(numLessThanFive,new ParameterExpression[] { numParam });
# Visitor を使用して式の構造を調べる
ExpressionVisitor のいくつかのメソッドをオーバーライドして、新しいビジター クラスを定義します。
class PrintingVisitor : ExpressionVisitor {
protected override Expression VisitConstant(ConstantExpression node) {
Console.WriteLine("Constant: {0}", node);
return base.VisitConstant(node);
}
protected override Expression VisitParameter(ParameterExpression node) {
Console.WriteLine("Parameter: {0}", node);
return base.VisitParameter(node);
}
protected override Expression VisitBinary(BinaryExpression node) {
Console.WriteLine("Binary with operator {0}", node.NodeType);
return base.VisitBinary(node);
}
}
Visit
に電話する このビジターを既存の式で使用するには:
Expression<Func<int,bool>> isBig = a => a > 1000000;
var visitor = new PrintingVisitor();
visitor.Visit(isBig);
# 式 API を理解する
式ツリー API を使用して CalculateSalesTax
を作成します。 木。平易な英語で、ツリーを作成するために必要な手順の概要を次に示します。
//For reference, we're using the API to build this lambda expression
orderLine => orderLine.IsTaxable ? orderLine.Total * orderLine.Order.TaxRate : 0;
//The orderLine parameter we pass in to the method. We specify it's type (OrderLine) and the name of the parameter.
ParameterExpression orderLine = Expression.Parameter(typeof(OrderLine), "orderLine");
//Check if the parameter is taxable; First we need to access the is taxable property, then check if it's true
PropertyInfo isTaxableAccessor = typeof(OrderLine).GetProperty("IsTaxable");
MemberExpression getIsTaxable = Expression.MakeMemberAccess(orderLine, isTaxableAccessor);
UnaryExpression isLineTaxable = Expression.IsTrue(getIsTaxable);
//Before creating the if, we need to create the braches
//If the line is taxable, we'll return the total times the tax rate; get the total and tax rate, then multiply
//Get the total
PropertyInfo totalAccessor = typeof(OrderLine).GetProperty("Total");
MemberExpression getTotal = Expression.MakeMemberAccess(orderLine, totalAccessor);
//Get the order
PropertyInfo orderAccessor = typeof(OrderLine).GetProperty("Order");
MemberExpression getOrder = Expression.MakeMemberAccess(orderLine, orderAccessor);
//Get the tax rate - notice that we pass the getOrder expression directly to the member access
PropertyInfo taxRateAccessor = typeof(Order).GetProperty("TaxRate");
MemberExpression getTaxRate = Expression.MakeMemberAccess(getOrder, taxRateAccessor);
//Multiply the two - notice we pass the two operand expressions directly to multiply
BinaryExpression multiplyTotalByRate = Expression.Multiply(getTotal, getTaxRate);
//If the line is not taxable, we'll return a constant value - 0.0 (decimal)
ConstantExpression zero = Expression.Constant(0M);
//Create the actual if check and branches
ConditionalExpression ifTaxableTernary = Expression.Condition(isLineTaxable, multiplyTotalByRate, zero);
//Wrap the whole thing up in a "method" - a LambdaExpression
Expression<Func<OrderLine, decimal>> method = Expression.Lambda<Func<OrderLine, decimal>>(ifTaxableTernary, orderLine);
# 構文
- Expression
name =lambdaExpression;
# パラメータ
パラメータ | 詳細th> |
---|---|
TDelegate | 式に使用されるデリゲート タイプ |
ラムダ式 | ラムダ式 (例:num => num < 5 ) |
# コメント
# 式ツリーの紹介
# どこから来たのか
式ツリーとは、実行時に「ソース コード」を使用することです。販売注文 decimal CalculateTotalTaxDue(SalesOrder order)
に課せられる消費税を計算する方法を考えてみましょう .このメソッドを .NET プログラムで使用するのは簡単です — decimal taxDue = CalculateTotalTaxDue(order);
と呼ぶだけです .リモート クエリ (SQL、XML、リモート サーバーなど) からのすべての結果に適用したい場合はどうすればよいでしょうか。これらのリモート クエリ ソースはメソッドを呼び出すことができません!従来は、これらすべてのケースでフローを逆にする必要がありました。クエリ全体を作成してメモリに保存し、結果をループして各結果の税金を計算します。
フロー反転のメモリとレイテンシの問題を回避する方法
式ツリーは、各ノードが式を保持するツリー形式のデータ構造です。それらは、コンパイルされた命令 (データをフィルター処理するために使用されるメソッドなど) を、データベース クエリ内などのプログラム環境の外で使用できる式に変換するために使用されます。
ここでの問題は、リモート クエリがメソッドにアクセスできないことです。 .代わりに指示を送信すれば、この問題を回避できます リモートクエリへのメソッド。 CalculateTotalTaxDue
では たとえば、次の情報を送信することを意味します:
これらの指示により、リモート クエリはデータを作成しながら作業を実行できます。
これを実装するには 2 つの課題があります。コンパイルされた .NET メソッドを命令のリストにどのように変換し、リモート システムで使用できるように命令をどのようにフォーマットしますか?
式ツリーがなければ、MSIL では最初の問題しか解決できませんでした。 (MSIL は、.NET コンパイラによって作成されるアセンブラーに似たコードです。)MSIL の解析は可能です。 、しかし、それは簡単ではありません。適切に解析したとしても、特定のルーチンに対する元のプログラマーの意図が何であったかを判断するのは難しい場合があります。
エクスプレッション ツリーで問題を解決
式ツリーは、まさにこれらの問題に対処します。これらは、各ノードが1つの命令を表すツリーデータ構造でプログラム命令を表します その命令を実行するために必要なすべての情報への参照があります。たとえば、MethodCallExpression
1) MethodInfo
を参照しています 2) Expression
のリスト s そのメソッドに渡されます。3) インスタンス メソッドの場合、Expression
メソッドを呼び出します。 「ツリーをたどって」リモートクエリに指示を適用できます。
式ツリーの作成
式ツリーを作成する最も簡単な方法は、ラムダ式を使用することです。これらの式は、通常の C# メソッドとほとんど同じように見えます。これがコンパイラ マジックであることを認識することが重要です .ラムダ式を初めて作成するとき、コンパイラはそれを代入する対象をチェックします。Delegate
の場合 タイプ (Action
を含む) または Func
)、コンパイラはラムダ式をデリゲートに変換します。 LambdaExpression
の場合 (または Expression<Action<T>>
または Expression<Func<T>>
強く型付けされた LambdaExpression
の)、コンパイラはそれを LambdaExpression
に変換します .ここで魔法がかかります。舞台裏で、コンパイラは式ツリー API を使用します。 ラムダ式を LambdaExpression
に変換するには .
ラムダ式は、すべてのタイプの式ツリーを作成できるわけではありません。そのような場合、Expressions API を手動で使用して、必要なツリーを作成できます。式を理解する API の例では、CalculateTotalSalesTax
を作成します。 API を使用した式。
注:ここでは、名前が少し混乱します。 ラムダ式 (2 つの単語、小文字) は、=>
を持つコードのブロックを指します。 インジケータ。これは C# の匿名メソッドを表し、Delegate
のいずれかに変換されます。 または Expression
. LambdaExpression
(一言で言えば PascalCase) は、実行可能なメソッドを表す Expression API 内のノード タイプを指します。
# 式ツリーと LINQ
式ツリーの最も一般的な用途の 1 つは、LINQ およびデータベース クエリでの使用です。 LINQ は、式ツリーとクエリ プロバイダーを組み合わせて、指示をターゲットのリモート クエリに適用します。たとえば、LINQ to Entity Framework クエリ プロバイダーは、式ツリーを SQL に変換し、データベースに対して直接実行します。
すべての要素を組み合わせると、LINQ の背後にある真の力がわかります。
<オール>products.Where(x => x.Cost > 5)
SELECT * FROM products WHERE Cost > 5
# メモ
- 式ツリーは不変です。式ツリーを変更する場合は、新しいものを作成する必要があります。既存のものを新しいものにコピーします (式ツリーをトラバースするには、
ExpressionVisitor
を使用できます)。 )、必要な変更を行います。