출처 : https://www.tutorialsteacher.com/linq/linq-expression
Expression Tree란?
코드를 정의하는 데이터 구조
expression tree의 node는 하나의 expression이다.
expresstion node의 ExpressionType은 System.Linq.Expressions.ExpressionType enum으로 종류를 확인할 수 있다.
lambda expression의 명확한 구조를 만든다.
namespace: System.Linq.Expressions.Expression
var sum = 1 + 2;
위의 코드를 tree로 만들면 아래와 같다.
- Variable declaration statement with assignment (var sum = 1 + 2;)
- Implicit variable type declaration (var sum)
- Implicit var keyword (var)
- Variable name declaration (sum)
- Assignment operator (=)
- Binary addition expression (1 + 2)
- Left operand (1)
- Addition operator (+)
- Right operand (2)
- Implicit variable type declaration (var sum)
Expression과 Delegate와 차이점
.NET Compiler는 compile time에 Func, Action type delegate에 할당된 lambda expression을 실행가능한 코드로 변환한다. Enumerable static class는 IEnumerable<T>를 구현하는 in-memory collections(List<T>, Dictionary<T>...)을 위한 extension method를 포함한다. 이 extension method들은 Func type delegate를 parameter로 사용한다.
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
List는 IEnumerable interface를 상속받아 iterate될 수 있고, Whre의 함수는 list에 직접 접근해서 실행된다.
var list = new List<int>()
.Where(n => n % 2 == 0);
.NET Compiler는 compile time에 Expression<TDelegate>에 할당된 lambda expression을 실행가능한 코드로 변환하는 대신에 Expression tree로 변환한다.
Queryable static class에는 expression type의 predicate를 parameter로 사용하는 extension methods를 포함하고 있다. predicate expression은 expression tree로 변경되고 remote LINQ provider에 data structure로 전달되어, expression tree로부터 적당한 쿼리를 생성하고 실행하게 한다.
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
EntityFramework를 사용하는 경우, list에 직접 접근할 수 없다. 왜냐하면 데이터는 in-memory에 있지 않고 db server에 있기 때문이다. 따라서 expression type의 delegate를 전달한다.
var list2 = new List<int>().AsQueryable()
.Where(n => n % 2 == 0);
[UseDbContext(typeof(ApplicationDbContext))]
[UseProjection]
[UseFiltering]
[UseSorting]
public Task<Author?> GetAuthorByIdAsync(int id, [ScopedService] ApplicationDbContext context)
=> context.Authors.FirstOrDefaultAsync(a => a.AuthorId == id);
[UseDbContext(typeof(ApplicationDbContext))]
[UseProjection]
[UseFiltering]
[UseSorting]
public Task<Author?> GetAuthorByIdAsyncWithExpres(int id, [ScopedService] ApplicationDbContext context)
{
System.Linq.Expressions.Expression<Func<Author, bool>> sameId = s => s.AuthorId < id;
return context.Authors.Where(sameId).FirstOrDefaultAsync();
}
Expression 선언
public class Student
{
public int StudentID { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
}
Func<Student, bool> isTeenAger = s => s.Age > 12 && s.Age < 20;
Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Expression<Action<Student>> printStudentName = s => Console.WriteLine(s.StudentName);
Func delegate를 Product라 하면, Func delegate Expression 은 Recipe라 할 수 있다.
Expression 실행
Expression으로 감싸진 delegate를 실행할 수 있다.
우선 Compile() 함수로 compile한 후, 반환된 Func or Action delegate를 invoke 할 수 있다.
xpression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
//compile Expression using Compile method to invoke it as Delegate
Func<Student, bool> isTeenAger = isTeenAgerExpr.Compile();
//Invoke
bool result = isTeenAger(new Student(){ StudentID = 1, StudentName = "Steve", Age = 20});
Expression, Expression tree
Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;
compiler는 위의 expression을 아래의 expression tree로 번역한다.
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 });
수동으로 Expression tree 구성하기
Expression은 abstract class로 수동으로 Expression tree를 만들 수 있는 static helper method를 포함하고 있다.
다음 lambda expression을 expression tree로 구성해보자.
Func<Student, bool> isAdult = s => s.age >= 18;
Func type delegate는 아래와 같다.
public bool function(Student s)
{
return s.Age > 18;
}
Step 1: parameter name이 s인 Student type parameter expression을 만들어야 한다.
ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
Step 2: property expression을 생성한다.
MemberExpression me = Expression.Property(pe, "Age");
Step 3: constant expression을 생성한다.
ConstantExpression constant = Expression.Constant(18, typeof(int));
Step 4: binary expression을 생성한다.
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
Step 5: Lambda expression을 생성한다.
var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
전체 소스 코드
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("Expression Tree: {0}", ExpressionTree);
Console.WriteLine("Expression Tree Body: {0}", ExpressionTree.Body);
Console.WriteLine("Number of Parameters in Expression Tree: {0}",
ExpressionTree.Parameters.Count);
Console.WriteLine("Parameters in Expression Tree: {0}", ExpressionTree.Parameters[0]);
Expression Tree를 왜 사용하는가?
Expression tree는 query expression이 다른 process로 전달되어 실행되는 string으로 변경되는 것 같은 변환하는 code 작업을 위해 만들어졌다.
Entity Framework의 LINQ API는 Expression Trees를 argument로 사용하여, C#으로 작성한 query를 SQL로 변역하여 database engine에서 실행하도록 한다.
'C#' 카테고리의 다른 글
C#] Delegates, Anonymous Methods and Lambda Expressions (0) | 2022.07.18 |
---|---|
C#] DynamicObject Class VS ExpandoObject Class (0) | 2022.07.07 |
C#] Delegates (0) | 2022.07.06 |
C#] 코드 성능 분석 BenchmarkDotNet (0) | 2022.07.05 |
C#] Anonymous Type(무명 형식) 정의, 사용법, Dynamic Type과 비교 (0) | 2022.06.28 |
댓글