サーチ…
前書き
構文
- 式<TDelegate> name = lambdaExpression;
パラメーター
パラメータ | 詳細 |
---|---|
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
のリスト、3)メソッドのメソッド、メソッドを呼び出すExpression
を参照します。あなたは "ツリーを歩いて"あなたのリモートクエリに指示を適用することができます。 式ツリーの作成
式ツリーを作成する最も簡単な方法は、ラムダ式を使用する方法です。これらの式は、通常のC#メソッドとほぼ同じです。これがコンパイラの魔法であることを理解することが重要です。最初にラムダ式を作成すると、コンパイラはそのラムダ式に何を割り当てるかをチェックします。Delegate
型( Action
またはFunc
を含む)の場合、コンパイラはラムダ式をデリゲートに変換します。 LambdaExpression
(または厳密に型指定されたLambdaExpression
のExpression<Action<T>>
またはExpression<Func<T>>
)の場合、コンパイラはLambdaExpression
変換します。背後では、コンパイラは式ツリーAPIを使ってラムダ式をLambdaExpression
に変換します。 ラムダ式は、あらゆるタイプの式ツリーを作成することはできません。そのような場合は、Expressions APIを手動で使用して、必要なツリーを作成することができます。 式APIの理解の例では、 APIを使用してCalculateTotalSalesTax
式を作成します。
注:名前はここで少し混乱します。 ラムダ式 (2つの単語、小文字)は、 =>
標識付きのコードブロックを参照します。これはC#での匿名メソッドを表し、 Delegate
またはExpression
変換されます。 LambdaExpression
(1つの単語、PascalCase)は、実行可能なメソッドを表すExpression API内のノード型を参照します。
式ツリーとLINQ
式ツリーの最も一般的な用途の1つは、LINQとデータベースクエリです。 LINQは式ツリーをクエリプロバイダとペアにして、対象のリモートクエリに命令を適用します。たとえば、LINQ to Entity Frameworkクエリプロバイダは、式ツリーをSQLに変換し、データベースに対して直接実行します。
すべての要素をまとめてみると、LINQの真のパワーを見ることができます。
- ラムダ式を使用してクエリを書く:
products.Where(x => x.Cost > 5)
- コンパイラは、その式を式ツリーに変換し、 "パラメータのCostプロパティが5より大きいかどうかを確認する"という指示を出します。
- 問合せプロバイダは式ツリーを解析し、有効なSQL問合せを生成します
SELECT * FROM products WHERE Cost > 5
- ORMはすべての結果をPOCOに投影し、オブジェクトのリストを取得します
ノート
- 式ツリーは不変です。式ツリーを変更するには、新しいツリーを作成する必要があります。既存のツリーを新しいツリーにコピーし(
ExpressionVisitor
Treeを使用して、ExpressionVisitor
を使用できます)、必要な変更を行います。
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
ラムダ式による式ツリーの作成
以下は、ラムダによって作成された最も基本的な式ツリーです。
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の理解
式ツリーAPIを使用してCalculateSalesTax
ツリーを作成します。簡単な英語で、ツリーの作成に要するステップの概要を以下に示します。
- 商品が課税対象かどうかを確認する
- そうであれば、行合計に適用税率を掛けてその金額を返します
- そうでなければ0を返す
//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 Treesを使用すると、実行可能コードの動的修正、さまざまなデータベースでのLINQクエリの実行、および動的クエリの作成が可能になります。式ツリーで表されるコードをコンパイルして実行することができます。
これらは動的言語ランタイム(DLR)で使用され、動的言語と.NET Framework間の相互運用性を提供し、コンパイラライターがMicrosoft中間言語(MSIL)の代わりに式ツリーを出力できるようにします。
表現木を作成することができます
- 匿名ラムダ式、
- 手動でSystem.Linq.Expressions名前空間を使用します。
ラムダ式からの式木
ラムダ式がExpression型変数に代入されると、コンパイラはラムダ式を表す式ツリーを構築するコードを発行します。
次のコード例は、C#コンパイラがラムダ式num => num <5を表す式ツリーを作成する方法を示しています。
Expression<Func<int, bool>> lambda = num => num < 5;
APIを使用した表現木
式ツリーは、 式クラスを使用しても作成されます。このクラスには、特定のタイプの式ツリーノードを作成する静的ファクトリメソッドが含まれています。
以下は、いくつかのタイプのツリーノードです。
- ParameterExpression
- MethodCallExpression
次のコード例は、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 });
ビジターを使って式の構造を調べる
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);