C# Language
표현식 트리
수색…
소개
통사론
- 식 <TDelegate> 이름 = lambdaExpression;
매개 변수
매개 변수 | 세부 |
---|---|
TDelegate | 표현식에 사용될 델리게이트 유형 |
lambdaExpression | 람다 식 (예 : num => num < 5 ) |
비고
표현식 트리 소개
우리가 온 곳
표현식 트리는 모두 런타임시 "소스 코드"를 소비합니다. 판매 주문 decimal CalculateTotalTaxDue(SalesOrder order)
에 대한 판매 세를 계산하는 방법을 고려해보십시오. .NET 프로그램 decimal taxDue = CalculateTotalTaxDue(order);
메서드를 사용하는 것은 쉽습니다. 그냥 decimal taxDue = CalculateTotalTaxDue(order);
. 원격 쿼리 (SQL, XML, 원격 서버 등)의 모든 결과에 적용하려면 어떻게해야합니까? 이러한 원격 쿼리 소스는 메서드를 호출 할 수 없습니다! 전통적으로이 모든 경우에 흐름을 뒤집어 야합니다. 전체 쿼리를 만들고이를 메모리에 저장 한 다음 결과를 반복하고 각 결과에 대해 세금을 계산하십시오.
플로우 인 버전의 메모리 및 대기 시간 문제를 방지하는 방법
표현식 트리는 각 노드가 표현식을 보유하는 트리 형식의 데이터 구조입니다. 그것들은 데이타베이스 질의와 같은 프로그램 환경 밖에서 사용될 수있는 표현식에서 컴파일 된 명령어 (데이터 필터링에 사용 된 메소드와 같은)를 변환하는 데 사용됩니다.
여기서 문제는 원격 쿼리 가 메서드에 액세스 할 수 없다는 것 입니다. 대신이 메소드에 대한 명령어 를 원격 쿼리에 보낸 경우이 문제를 피할 수 있습니다. CalculateTotalTaxDue
예제에서이 정보를 보내는 것을 의미합니다.
- 총 세금을 저장할 변수 만들기
- 주문의 모든 라인을 반복하십시오.
- 각 라인에 대해 제품이 과세 대상인지 확인하십시오.
- 그러한 경우 총액에 적용 가능한 세율을 곱하고 그 금액을 총액에 더하십시오.
- 그렇지 않으면 아무것도하지 않습니다.
이러한 지침을 통해 원격 쿼리는 데이터를 생성하는 동안 작업을 수행 할 수 있습니다.
이를 구현하는 데에는 두 가지 문제점이 있습니다. 컴파일 된 .NET 메소드를 명령어 목록으로 변환하는 방법과 명령어를 원격 시스템에서 사용할 수있는 방식으로 포맷하는 방법은 무엇입니까?
표현식 트리가 없으면 MSIL의 첫 번째 문제 만 해결할 수 있습니다. (MSIL은 .NET 컴파일러에서 만든 어셈블러 같은 코드입니다.) MSIL 구문 분석이 가능 하지만 쉽지 않습니다. 제대로 파싱한다고하더라도 원래 프로그래머의 의도가 특정 루틴과 무엇인지 판단하기가 어려울 수 있습니다.
식 트리를 사용하면 하루를 절약 할 수 있습니다.
식 트리는 이러한 정확한 문제를 해결합니다. 그것들은 프로그램 명령을 트리 데이터 구조로 나타내며 각 노드는 하나의 명령을 나타내며 해당 명령을 실행하는 데 필요한 모든 정보를 나타냅니다. 예를 들어MethodCallExpression
은 1) 호출 할 MethodInfo
, 2) 해당 메서드에 전달할 Expression
의 목록, 3) 인스턴스 메서드, 메서드를 호출 할 Expression
합니다. "나무를 걷고"원격 쿼리에 대한 지침을 적용 할 수 있습니다. 표현 트리 만들기
표현식 트리를 만드는 가장 쉬운 방법은 람다 식을 사용하는 것입니다. 이 표현식은 일반적인 C # 메소드와 거의 같습니다. 이것이 컴파일러 마술 임을 깨닫는 것이 중요합니다. 처음에 람다 식을 만들면 컴파일러는 사용자가 할당 한 내용을 확인합니다.Delegate
유형 ( Action
또는 Func
) 인 경우 컴파일러는 람다 식을 대리자로 변환합니다. LambdaExpression
(또는 강력하게 형식화 된 LambdaExpression
인 Expression<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의 진정한 힘을 알 수 있습니다.
- 람다 식을 사용하여 쿼리를 작성하십시오 :
products.Where(x => x.Cost > 5)
- 컴파일러는 "매개 변수의 Cost 속성이 5보다 큰지 확인"지침에 따라 해당 식을 표현식 트리로 변환합니다.
- 쿼리 공급자가 식 트리를 구문 분석하고 유효한 SQL 쿼리를 만듭니다.
SELECT * FROM products WHERE Cost > 5
- 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
. 일반 영어로 트리를 만드는 데 필요한 단계를 요약하면 다음과 같습니다.
- 제품이 과세 대상인지 확인하십시오.
- 있는 경우 해당 총액에 해당 세율을 곱하여 해당 금액을 반환하십시오.
- 그렇지 않으면 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) 대신 표현식 트리를 내보낼 수있게합니다.
표현식 트리를 통해 만들 수 있음
- 익명 람다 식,
- System.Linq.Expressions 네임 스페이스를 사용하여 수동으로.
람다 표현식의 표현식 트리
람다식이 식 유형 변수에 할당되면 컴파일러는 람다 식을 나타내는 식 트리를 작성하는 코드를 내 보냅니다.
다음 코드 예제에서는 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);