수색…
비고
식 트리는 .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
toSQL
을 사용하여 조건자를 테스트하는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이 발생하면 실패합니다.