サーチ…


備考

式ツリーは、.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 to SQLを使用して、述語をテストする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で失敗します。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow