サーチ…
備考
式ツリーは、.NET Frameworkのコード式を表すために使用されるデータ構造です。コードで生成し、プログラムでトラバースしてコードを別の言語に翻訳したり、実行したりすることができます。 Expression Treesの最も一般的なジェネレータはC#コンパイラです。ラムダ式がExpression <Func <... >>型の変数に割り当てられている場合、C#コンパイラは式ツリーを生成できます。通常、これはLINQのコンテキストで発生します。最も一般的なコンシューマは、Entity FrameworkのLINQプロバイダです。 Entity Frameworkに与えられた式ツリーを消費し、同等のSQLコードを生成し、それをデータベースに対して実行します。
C#コンパイラで生成される単純な式ツリー
次のC#コードを考えてみましょう
Expression<Func<int, int>> expression = a => a + 1;
C#コンパイラでは、ラムダ式がデリゲート型ではなくExpression型に割り当てられていることがわかるため、このコードとほぼ同等の式ツリーが生成されます
ParameterExpression parameterA = Expression.Parameter(typeof(int), "a");
var expression = (Expression<Func<int, int>>)Expression.Lambda(
Expression.Add(
parameterA,
Expression.Constant(1)),
parameterA);
ツリーのルートは、本体とパラメーターのリストを含むラムダ式です。ラムダには "a"という1つのパラメータがあります。本文は、CLR型BinaryExpressionとAddのNodeTypeの1つの式です。この式は加算を表します。左と右の2つの部分式があります。 leftはパラメータ "a"のParameterExpressionで、Rightは値1のConstantExpressionです。
この式の最も単純な使い方は、それを印刷することです。
Console.WriteLine(expression); //prints a => (a + 1)
同等のC#コードを出力します。
式ツリーはC#デリゲートにコンパイルされ、CLRによって実行されます
Func<int, int> lambda = expression.Compile();
Console.WriteLine(lambda(2)); //prints 3
通常、式はSQLのような他の言語に変換されますが、Reflectionの代わりに公開型または非公開型のプライベート、プロテクト、および内部メンバーを呼び出すためにも使用できます。
フォームフィールド==値の述語を構築する
実行時に_ => _.Field == "VALUE"
ような式を構築する。
述部_ => _.Field
と文字列値"VALUE"
が指定されている場合、述部が真であるかどうかをテストする式を作成します。
式は次のものに適しています。
-
IQueryable<T>
、IEnumerable<T>
を使用して述語をテストします。 - エンティティフレームワークまたは
Linq
toSQL
を使用して、述語をテストするWhere
句を作成しSQL
。
このメソッドは、 Field
が"VALUE"
等しいかどうかをテストする適切なEqual
式を構築します。
public static Expression<Func<T, bool>> BuildEqualPredicate<T>(
Expression<Func<T, string>> memberAccessor,
string term)
{
var toString = Expression.Convert(Expression.Constant(term), typeof(string));
Expression expression = Expression.Equal(memberAccessor.Body, toString);
var predicate = Expression.Lambda<Func<T, bool>>(
expression,
memberAccessor.Parameters);
return predicate;
}
述語は、 Where
拡張メソッドに述語を含めることによって使用できます。
var predicate = PredicateExtensions.BuildEqualPredicate<Entity>(
_ => _.Field,
"VALUE");
var results = context.Entity.Where(predicate).ToList();
静的フィールドを取得するための式
次のようなタイプの例があります:
public TestClass
{
public static string StaticPublicField = "StaticPublicFieldValue";
}
StaticPublicFieldの値を取得できます。
var fieldExpr = Expression.Field(null, typeof(TestClass), "StaticPublicField");
var labmda = Expression.Lambda<Func<string>>(fieldExpr);
それは、フィールド値を取り出すためのデリゲートにコンパイルすることができます。
Func<string> retriever = lambda.Compile();
var fieldValue = retriever();
// fieldValueの結果はStaticPublicFieldValueです。
InvocationExpressionクラス
InvocationExpressionクラスを使用すると、同じExpressionツリーの一部である他のラムダ式を呼び出すことができます。
静的なExpression.Invoke
メソッドで作成します。
問題私たちは、その記述に「車」を持つアイテムを手に入れたいと思っています。内部で文字列を検索する前にnullをチェックする必要がありますが、計算が高価になる可能性があるので、それを過度に呼びたくはありません。
using System;
using System.Linq;
using System.Linq.Expressions;
public class Program
{
public static void Main()
{
var elements = new[] {
new Element { Description = "car" },
new Element { Description = "cargo" },
new Element { Description = "wheel" },
new Element { Description = null },
new Element { Description = "Madagascar" },
};
var elementIsInterestingExpression = CreateSearchPredicate(
searchTerm: "car",
whereToSearch: (Element e) => e.Description);
Console.WriteLine(elementIsInterestingExpression.ToString());
var elementIsInteresting = elementIsInterestingExpression.Compile();
var interestingElements = elements.Where(elementIsInteresting);
foreach (var e in interestingElements)
{
Console.WriteLine(e.Description);
}
var countExpensiveComputations = 0;
Action incCount = () => countExpensiveComputations++;
elements
.Where(
CreateSearchPredicate(
"car",
(Element e) => ExpensivelyComputed(
e, incCount
)
).Compile()
)
.Count();
Console.WriteLine("Property extractor is called {0} times.", countExpensiveComputations);
}
private class Element
{
public string Description { get; set; }
}
private static string ExpensivelyComputed(Element source, Action count)
{
count();
return source.Description;
}
private static Expression<Func<T, bool>> CreateSearchPredicate<T>(
string searchTerm,
Expression<Func<T, string>> whereToSearch)
{
var extracted = Expression.Parameter(typeof(string), "extracted");
Expression<Func<string, bool>> coalesceNullCheckWithSearch =
Expression.Lambda<Func<string, bool>>(
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrEmpty", null, extracted)
),
Expression.Call(extracted, "Contains", null, Expression.Constant(searchTerm))
),
extracted);
var elementParameter = Expression.Parameter(typeof(T), "element");
return Expression.Lambda<Func<T, bool>>(
Expression.Invoke(
coalesceNullCheckWithSearch,
Expression.Invoke(whereToSearch, elementParameter)
),
elementParameter
);
}
}
出力
element => Invoke(extracted => (Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car")), Invoke(e => e.Description, element))
car
cargo
Madagascar
Predicate is called 5 times.
最初に注意すべき点は、Invokeでラップされた実際のアクセス権のアクセス方法です。
Invoke(e => e.Description, element)
これはe.Description
に触れる唯一の部分であり、その代わりにstring
型のextracted
パラメータが次のものに渡されstring
:
(Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car"))
ここで注目すべきもう一つの重要なことは、 AndAlso
です。最初の部分が 'false'を返した場合は、左部分のみを計算します。ビット演算子 'And'を使用するのはよくある間違いです。この演算子は常に両方の部分を計算し、この例ではNullReferenceExceptionで失敗します。