수색…


소개

표현식 트리는 트리 형태의 데이터 구조로 배열 된 표현식입니다. 트리의 각 노드는 표현식의 표현이며, 표현식은 코드입니다. Lambda 표현식의 메모리 내 표현은 표현식 트리가 될 것이고, 이는 쿼리의 실제 요소 (예 : 코드)는 포함하지만 결과는 포함하지 않습니다. 표현식 트리는 람다 표현식의 구조를 투명하고 명시 적으로 만듭니다.

통사론

  • 식 <TDelegate> 이름 = lambdaExpression;

매개 변수

매개 변수 세부
TDelegate 표현식에 사용될 델리게이트 유형
lambdaExpression 람다 식 (예 : num => num < 5 )

비고

표현식 트리 소개

우리가 온 곳

표현식 트리는 모두 런타임시 "소스 코드"를 소비합니다. 판매 주문 decimal CalculateTotalTaxDue(SalesOrder order) 에 대한 판매 세를 계산하는 방법을 고려해보십시오. .NET 프로그램 decimal taxDue = CalculateTotalTaxDue(order); 메서드를 사용하는 것은 쉽습니다. 그냥 decimal taxDue = CalculateTotalTaxDue(order); . 원격 쿼리 (SQL, XML, 원격 서버 등)의 모든 결과에 적용하려면 어떻게해야합니까? 이러한 원격 쿼리 소스는 메서드를 호출 할 수 없습니다! 전통적으로이 모든 경우에 흐름을 뒤집어 야합니다. 전체 쿼리를 만들고이를 메모리에 저장 한 다음 결과를 반복하고 각 결과에 대해 세금을 계산하십시오.

플로우 인 버전의 메모리 및 대기 시간 문제를 방지하는 방법

표현식 트리는 각 노드가 표현식을 보유하는 트리 형식의 데이터 구조입니다. 그것들은 데이타베이스 질의와 같은 프로그램 환경 밖에서 사용될 수있는 표현식에서 컴파일 된 명령어 (데이터 필터링에 사용 된 메소드와 같은)를 변환하는 데 사용됩니다.

여기서 문제는 원격 쿼리 가 메서드에 액세스 할 수 없다는 것 입니다. 대신이 메소드에 대한 명령어 를 원격 쿼리에 보낸 경우이 문제를 피할 수 있습니다. CalculateTotalTaxDue 예제에서이 정보를 보내는 것을 의미합니다.

  1. 총 세금을 저장할 변수 만들기
  2. 주문의 모든 라인을 반복하십시오.
  3. 각 라인에 대해 제품이 과세 대상인지 확인하십시오.
  4. 그러한 경우 총액에 적용 가능한 세율을 곱하고 그 금액을 총액에 더하십시오.
  5. 그렇지 않으면 아무것도하지 않습니다.

이러한 지침을 통해 원격 쿼리는 데이터를 생성하는 동안 작업을 수행 할 수 있습니다.

이를 구현하는 데에는 두 가지 문제점이 있습니다. 컴파일 된 .NET 메소드를 명령어 목록으로 변환하는 방법과 명령어를 원격 시스템에서 사용할 수있는 방식으로 포맷하는 방법은 무엇입니까?

표현식 트리가 없으면 MSIL의 첫 번째 문제 만 해결할 수 있습니다. (MSIL은 .NET 컴파일러에서 만든 어셈블러 같은 코드입니다.) MSIL 구문 분석이 가능 하지만 쉽지 않습니다. 제대로 파싱한다고하더라도 원래 프로그래머의 의도가 특정 루틴과 무엇인지 판단하기가 어려울 수 있습니다.

식 트리를 사용하면 하루를 절약 할 수 있습니다.

식 트리는 이러한 정확한 문제를 해결합니다. 그것들은 프로그램 명령을 트리 데이터 구조로 나타내며 각 노드는 하나의 명령을 나타내며 해당 명령을 실행하는 데 필요한 모든 정보를 나타냅니다. 예를 들어 MethodCallExpression 은 1) 호출 할 MethodInfo , 2) 해당 메서드에 전달할 Expression 의 목록, 3) 인스턴스 메서드, 메서드를 호출 할 Expression 합니다. "나무를 걷고"원격 쿼리에 대한 지침을 적용 할 수 있습니다.

표현 트리 만들기

표현식 트리를 만드는 가장 쉬운 방법은 람다 식을 사용하는 것입니다. 이 표현식은 일반적인 C # 메소드와 거의 같습니다. 이것이 컴파일러 마술 임을 깨닫는 것이 중요합니다. 처음에 람다 식을 만들면 컴파일러는 사용자가 할당 한 내용을 확인합니다. Delegate 유형 ( Action 또는 Func ) 인 경우 컴파일러는 람다 식을 대리자로 변환합니다. LambdaExpression (또는 강력하게 형식화 된 LambdaExpressionExpression<Action<T>> 또는 Expression<Func<T>> ) 인 경우 컴파일러는이를 LambdaExpression 으로 변환합니다. 이 부분에서 마술이 시작됩니다. 장면 뒤에서 컴파일러 는 expression tree API사용 하여 람다 식을 LambdaExpression 으로 변환합니다.

람다 식은 모든 유형의 식 트리를 만들 수 없습니다. 이러한 경우 Expressions API를 수동으로 사용하여 필요한 트리를 만들 수 있습니다. 표현식 API 이해하기 예제에서 API 를 사용하여 CalculateTotalSalesTax 표현식을 만듭니다.

참고 : 이름이 약간 혼란 스러울 수 있습니다. 람다 식 (두 단어, 소문자)은 => 표시기가있는 코드 블록을 나타냅니다. 그것은 C #에서 익명 메소드를 나타내며 Delegate 또는 Expression 으로 변환됩니다. LambdaExpression (한 단어, PascalCase)은 실행할 수있는 메서드를 나타내는 Expression API 내의 노드 형식을 참조합니다.

표현식 트리 및 LINQ

표현 트리의 가장 일반적인 용도 중 하나는 LINQ 및 데이터베이스 쿼리입니다. LINQ는 쿼리 공급자와 식 트리를 연결하여 대상 원격 쿼리에 지침을 적용합니다. 예를 들어, LINQ to Entity Framework 쿼리 공급자는 식 트리를 데이터베이스로 직접 변환하여 SQL로 변환합니다.

모든 조각을 모으면 LINQ의 진정한 힘을 알 수 있습니다.

  1. 람다 식을 사용하여 쿼리를 작성하십시오 : products.Where(x => x.Cost > 5)
  2. 컴파일러는 "매개 변수의 Cost 속성이 5보다 큰지 확인"지침에 따라 해당 식을 표현식 트리로 변환합니다.
  3. 쿼리 공급자가 식 트리를 구문 분석하고 유효한 SQL 쿼리를 만듭니다. SELECT * FROM products WHERE Cost > 5
  4. ORM은 모든 결과를 POCO에 투영하고 객체 목록을 얻습니다.

노트

  • 표현식 트리는 변경 불가능합니다. 표현식 트리를 변경하려면 새로운 표현식 트리를 만들고 기존 표현식 트리를 새로운 표현식 트리에 복사 한 다음 ( ExpressionVisitor 사용할 수 있도록 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 트리를 만들 CalculateSalesTax . 일반 영어로 트리를 만드는 데 필요한 단계를 요약하면 다음과 같습니다.

  1. 제품이 과세 대상인지 확인하십시오.
  2. 있는 경우 해당 총액에 해당 세율을 곱하여 해당 금액을 반환하십시오.
  3. 그렇지 않으면 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);

식 트리 기본

표현 트리는 노드와 같은 데이터 구조의 코드를 나타내며, 각 노드는 표현식입니다.

식 트리를 사용하면 실행 코드의 동적 수정, 다양한 데이터베이스의 LINQ 쿼리 실행 및 동적 쿼리 생성이 가능합니다. 표현식 트리가 나타내는 코드를 컴파일하고 실행할 수 있습니다.

또한 동적 언어 런타임 (DLR)에서 동적 언어와 .NET Framework 간의 상호 운용성을 제공하고 컴파일러 작성자가 Microsoft 중간 언어 (MSIL) 대신 표현식 트리를 내보낼 수있게합니다.

표현식 트리를 통해 만들 수 있음

  1. 익명 람다 식,
  2. System.Linq.Expressions 네임 스페이스를 사용하여 수동으로.

람다 표현식의 표현식 트리

람다식이 식 유형 변수에 할당되면 컴파일러는 람다 식을 나타내는 식 트리를 작성하는 코드를 내 보냅니다.

다음 코드 예제에서는 C # 컴파일러에서 람다 식 num => num <5를 나타내는 식 트리를 만드는 방법을 보여줍니다.

Expression<Func<int, bool>> lambda = num => num < 5;

API를 사용하여 표현식 트리 만들기

식 트리는 또한 클래스를 사용하여 작성됩니다. 이 클래스는 특정 유형의 표현식 트리 노드를 만드는 정적 팩토리 메소드를 포함합니다.

아래에는 몇 가지 유형의 트리 노드가 있습니다.

  1. ParameterExpression
  2. 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);


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow