English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

LINQ 표현식 트리

이전 장에서 표현식에 대해 배웠습니다. 지금은 표현식 트리에 대해 배워보겠습니다.

명확하게, 표현식 트리는 트리 구조 데이터로 배치된 표현식입니다. 표현식 트리의 각 노드는 표현식입니다. 예를 들어, 표현식 트리는 수학 공식 x < y를 표현할 수 있으며, x, <, y는 표현식으로 처리되어 트리 구조에 배치됩니다.

표현식 트리는 람다 표현식의 메모리 표현 형식입니다. 그것은 쿼리의 실제 요소를 저장하며, 쿼리의 결과를 저장하지 않습니다.

표현식 트리는 람다 표현식의 구조를 투명하고 명확하게 만듭니다. 표현식 트리 내의 데이터와 다른 데이터 구조와 마찬가지로 상호작용할 수 있습니다.

예를 들어, 다음 isTeenAgerExpr 표현식을 보겠습니다:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

컴파일러는 위의 표현식을 다음 표현식 트리로 변환합니다:

예제: C#의 표현식 트리

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

그리고도 표현식 트리를 수동으로 생성할 수 있습니다. 다음 간단한 lambda 표현식을 위해 표현식 트리를 어떻게 생성하는지 보겠습니다:

예제: C#의 Func 대리자:

Func<Student, bool> isAdult = s => s.age >= 18;

이 Func 타입 대리자는 다음 메서드로 간주됩니다:

 C#:

public bool function(Student s)
{
  return s.Age > 18;
}

표현식 트리를 생성하려면 먼저, Student는 파라미터 타입이고 's'는 파라미터 이름인 파라미터 표현식을 생성해야 합니다. 다음과 같이:

단계1C#에서 파라미터 표현식을 생성합니다

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

지금, Expression.Property()를 사용하여 s.Age 표현식을 생성합니다. s는 파라미터이고 Age는 Student의 프로퍼티 이름입니다. (Expression은 손으로 표현식 트리를 생성하기 위한 스태틱 헬퍼 메서드를 포함한 추상 클래스입니다.)

단계2C#에서 프로퍼티 표현식을 생성합니다

MemberExpression me = Expression.Property(pe, "Age");

지금,18고정 값 표현식을 생성합니다:

단계3C#에서 고정 값 표현식을 생성합니다

ConstantExpression constant = Expression.Constant(18, typeof(int));

지금까지 우리는 s.Age(멤버 표현식)과18고정 값 표현식을 사용하여 표현식 트리를 생성했습니다. 지금이야 멤버 표현식이 고정 값 표현식보다 크기를 확인해야 합니다. 이를 위해 Expression.GreaterThanOrEqual() 메서드를 사용하고 멤버 표현식과 고정 값 표현식을 전달하십시오:

단계4C#에서 이진 표현식을 생성합니다

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

따라서, 우리는 lambda 표현식 본문 s.Age> =을 위해 18 표현식 트리를 생성했습니다. 지금이야参数 표현식과 본문 표현식을 연결해야 합니다. Expression.Lambda를 사용하여(본문, 파라미터 배열) 연결된 lambda 표현식 s => s.age > = 18의 본문(본문)과 파라미터 파트:

단계5C#에서 Lambda 표현식을 생성합니다

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

이렇게 하면 lambda 표현식을 가진 간단한 Func 대리자를 위한 표현식 트리를 구축할 수 있습니다.

예제: C#의 표현식 트리

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
MemberExpression me = Expression.Property(pe, "Age");
ConstantExpression constant = Expression.Constant(18, typeof(int));
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
Console.WriteLine("표현식 트리: {0}", ExpressionTree);
        
Console.WriteLine("표현식 트리 본체: {0}", ExpressionTree.Body);
        
Console.WriteLine("표현식 트리 본체: {0}", ExpressionTree.Body) 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("표현식 트리의 매개변수: {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")
Dim mexp As MemberExpression = Expression.Property(pe, "Age")
Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))
Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)
Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) =
Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() { pe })
Console.WriteLine("표현식 트리: {0}", ExpressionTree)
        
Console.WriteLine("표현식 트리 본체: {0}", ExpressionTree.Body) 
                                Console.WriteLine("표현식 트리의 매개변수 개수: {0}", ExpressionTree.Parameters.Count)
        
Console.WriteLine("표현식 트리의 매개변수: {0}", ExpressionTree.Parameters(0))
출력:}}
표현식 트리: s => (s.Age >= 18)
표현식 트리 본체: (s.Age >= 18)
표현식 트리의 매개변수 개수: 1
표현식 트리의 매개변수: s

아래 그림은 표현식 트리를 생성하는 전체 과정을 설명합니다:

표현식 트리를 구성합니다

왜 표현식 트리를 선택하는가?

이전 장에서는 lambda 표현식에 할당된 이유를 알았습니다:Func<T>컴파일된 실행 코드는 lambda 표현식에 할당됩니다:Expression<TDelegate>타입을 Expression 트리로 컴파일합니다.

실행 코드는 동일한 애플리케이션 도메인에서 실행되어 메모리 중集合을 처리합니다. Enumerables의 정적 클래스는IEnumerable<T>인터페이스의 메모리 중集合의 확장 메서드, 예를 들어 List<T>,Dictionary<T> 등. Enumerable 클래스의 확장 메서드는Func타입 위임의 판정 파라미터를 받아들입니다. 예를 들어,Where확장 메서드는Func<TSource, bool> 판정그런 다음, 동일한 AppDomain 내의 메모리 중集合을 처리하기 위해 IL(중간 언어)로 컴파일합니다:

아래 그림은 Enumerable 클래스에서 Where 확장 메서드가 Func 위임을 파라미터로 포함하는 경우를 보여줍니다:

Where의 Func 위임

Func위임은 원본 실행 코드이므로, 코드 디버깅을 수행할 때는 다음과 같이 발견할 수 있습니다:Func위임은 불투명 코드로 표현됩니다. 그 파라미터, 반환 타입, 및 본체를 볼 수 없습니다:

디버깅 모드의 Func 대리자

Func대리자는 메모리 내의 콜렉션에 사용되며, AppDomain에서 동일하게 처리될 것이기 때문에 LINQ-to-SQL, EntityFramework, 또는 LINQ 기능을 제공하는 제3자 제품의 원격 LINQ 쿼리 제공자는 어떻게 되었는가? 그들은 원래 실행 코드로 컴파일된 lambda 표현식을 어떻게 파싱할 것인가? 파라미터, lambda 표현식의 반환형, 실행 중 쿼리를 구축하고 추가 처리를 위해 어떻게 처리할 것인가? 답은표현 트리.

Expression <TDelegate>는 표현식 트리로 알려진 데이터 구조로 컴파일됩니다.

디버깅 코드를 할 때 표현식은 다음과 같이 표현됩니다:

디버깅 모드의 표현식 트리

지금 그들은 일반 대리자와 표현식 간의 차이를 볼 수 있습니다. 표현식 트리는 투명합니다. 다음과 같이 표현식에서 파라미터, 반환형, 주체 표현식 정보를 검색할 수 있습니다:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expression: {0}", isTeenAgerExpr);
        
Console.WriteLine("표현식 유형: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("파라미터 이름: {0}", param.Name);
    Console.WriteLine("파라미터 유형: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("표현식 주체 왼쪽: {0}", bodyExpr.Left);
Console.WriteLine("이진 표현식 유형: {0}", bodyExpr.NodeType);
Console.WriteLine("표현식 주체 오른쪽: {0}", bodyExpr.Right);
Console.WriteLine("반환형: {0}", isTeenAgerExpr.ReturnType);
출력:}}
표현식: s => ((s.Age > 12) AndAlso (s.Age < 20))
표현식 유형: Lambda
파라미터 이름: s
파라미터 유형: Student
표현식 본체 왼쪽: (s.Age > 12)
이진 표현식 유형: AndAlso
표현식 본체 오른쪽: (s.Age < 20)
반환 유형: System.Boolean

LINQ에 대한 동일한 애플리케이션 도메인에서 실행되지 않는-to-SQL이나 Entity Framework의 LINQ 쿼리. 예를 들어, Entity Framework의 다음 LINQ 쿼리는 프로그램 내에서 실제로 실행되지 않습니다:

예제: C#의 LINQ 쿼리
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

먼저 SQL 문장으로 변환한 다음, 데이터베이스 서버에서 실행합니다.

쿼리 표현식에서 찾은 코드는 SQL 쿼리로 변환되어 문자열로 전달되어 다른 프로세스로 보내져야 합니다. LINQ의 경우-to-SQL이나 Entity Framework, 이 과정은 SQL Server 데이터베이스입니다. 표현식 트리와 같은 데이터 구조를 SQL로 변환하는 것은 원래 IL이나 실행 가능 코드를 SQL로 변환하는 것보다 훨씬 쉬우며, 이를 통해 표현식에서 정보를 추출하는 것이 쉽습니다.

표현식 트리를 생성하는 목적은 쿼리 표현식과 같은 코드를 다른 프로세스에 전달하고 여기서 실행할 수 있는 문자열로 변환하는 것입니다.

검색 가능한 정적 클래스는 Expression 유형의 판정자 매개변수를 받는 확장 메서드를 포함합니다. 이 판정자 표현식을 표현식 트리로 변환한 다음, 표현식 트리를 데이터 구조로 전달하여 제공 프로그램이 표현식 트리에서 적절한 쿼리를 구축하고 쿼리를 실행할 수 있도록 합니다.