수색…


비고

식 트리는 .NET Framework의 코드 식을 나타내는 데 사용되는 데이터 구조입니다. 코드에 의해 생성되고 프로그래밍 방식으로 탐색되어 코드를 다른 언어로 변환하거나 실행할 수 있습니다. Expression Trees의 가장 많이 사용되는 생성자는 C # 컴파일러입니다. C # 컴파일러는 람다식이 Expression <Func <... >> 형식의 변수에 할당 된 경우 식 트리를 생성 할 수 있습니다. 일반적으로 LINQ 컨텍스트에서 발생합니다. 가장 많이 사용되는 소비자는 Entity Framework의 LINQ 공급자입니다. Entity Framework에 제공된 식 트리를 사용하고 해당 SQL 코드를 생성 한 다음 데이터베이스에 대해 실행합니다.

C # 컴파일러에서 생성되는 간단한 표현식 트리

다음 C # 코드를 고려하십시오.

Expression<Func<int, int>> expression = a => a + 1;

C # 컴파일러에서는 람다식이 대리자 형식이 아닌 식 형식에 할당된다는 것을 알기 때문에이 코드와 대략 동일한 식 트리를 생성합니다

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 of NodeType의 단일 표현식입니다. 이 표현식은 덧셈을 나타냅니다. Left와 Right로 표시된 두 개의 서브 표현식을가집니다. 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 절을 만듭니다.

이 메서드는 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에 래핑 된 실제 propery 액세스 방법입니다.

Invoke(e => e.Description, element)

, 그리고 이것은 e.Description 을 만지는 유일한 부분이며, 대신에 string 타입의 extracted 매개 변수가 다음 매개 변수로 전달됩니다.

(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