2017-01-02 2 views
7

MSDN의 How to: Modify Expression Trees에 대한 기사에서 ExpressionVisitor은 (는) 어떻게 처리되는지를 알고 있습니다. 표현식을 수정해야합니다.왜 ExpressionVisitor를 사용하고 싶습니까?

그러나 그 예는 꽤 비현실적이어서 나는 왜 내가 그것을 필요로하는지 궁금해하고 있었습니까? 표현식 트리를 수정하는 것이 실제적인 사례를들 수 있습니까? 아니면 왜 전혀 수정해야합니까? 무엇부터 무엇까지?

또한 모든 종류의 표현을 방문하는 데 많은 오버로드가 있습니다. 언제 내가 그 중 어떤 것을 사용해야하며 어떻게 돌려야 하는지를 어떻게 알 수 있습니까? 나는 을 사용하고 다른 한 손으로 base.VisitParameter(node)을 돌려 보내는 사람들이 Expression.Parameter(..)을 반환하는 것을 보았습니다.

+2

트리 구조를 수정하려면 방문자가이를 달성하기위한 매우 표준적인 기술, 특히 변경 불가능한 트리의 경우가 많습니다. 이러한 방문자에 대한 기본 클래스를 제공합니다. 새 노드를 변경하고 반환해야하는 노드 유형의 메소드 만 대체합니다. 방문자는 전체적으로 점진적으로 새 트리를 만듭니다. –

+1

@LucasTrzesniewski 이것은 내가 이미 알아 낸 것입니다 (노드 유형이 없으면이 사실을 알지 못했습니다). 저는 전에 가질 수있는 것에 관심이 있으며 무엇이 나올지에 관심이 있습니다. 어떤 종류의 표현 트리가 무엇에서 무엇으로 수정되어야 하는가? 이것은 누락 된 링크입니다. – t3chb0t

+1

ㅎ, 대답은 * 당신이해야 할 일이 무엇이든합니다. 표현식 트리는 수정이 필요하지 않습니다. * 어떤 이유로 든 수정해야 할 수도 있습니다. 실제 예제를 보려면 [LINQKit] (https://github.com/scottksmith95/LINQKit)을 참조하십시오 (AsExpandable이 표현식을 다시 작성합니다). –

답변

3

표현 트리를 수정하는 것이 실제적인 사례를 말해 줄 수 있습니까?

엄밀히가 불변으로, 우리는 식 트리를 수정하지 말하기 (외부에서 볼 때, 적어도, 그것은 memoise 값하거나 변경 가능한 개인 상태가없는 내부적 않는 어떤 약속도 없다). 그것들은 불변이므로 정확히 우리가 가지고있는 표현식 트리를 기반으로 새로운 표현식 트리를 만들려면 방문자 패턴이 많은 노드를 변경할 수 없습니다. 우리가 불변 객체를 수정해야만하는 가장 가까운 것).

Linq 자체에서 몇 가지를 찾을 수 있습니다.

가장 간단한 Linq 공급자는 메모리의 열거 형 개체에서 작동하는 linq-to-objects 공급자입니다.

열거 형을 직접적으로 IEnumerable<T> 개체로 받으면 대부분의 프로그래머가 대부분의 메서드를 최적화되지 않은 버전으로 쉽게 작성할 수 있다는 점에서 매우 간단합니다. 예 : Where은 다음과 같습니다 등등

foreach (T item in source) 
    if (pred(item)) 
    yield return item; 

합니다. 그러나 EnumerableQueryableIQueryable<T> 버전을 구현하면 어떨까요? EnumerableQueryableIEnumerable<T>을 감싸기 때문에 관련된 하나 이상의 열거 가능한 객체에 대해 원하는 작업을 수행 할 수 있지만 우리는 해당 작업을 IQueryable<T> 및 선택자, 조건 자 등의 다른 표현식으로 설명하는 표현식을 사용합니다. 여기서 필요한 것은 설명입니다. 셀렉터 등, 술어 IEnumerable<T>의 관점에서 그 대리자 동작

System.Linq.EnumerableRewriterExpressionVisitor 구현 정확히 같은 재 기입을 수행하고, 그 결과는 단순히 컴파일되고 실행될 수있다.

System.Linq.Expressions 내에는 다양한 목적으로 ExpressionVisitor의 구현이 몇 가지 있습니다. 한 가지 예는 인터프리터 형식의 컴파일은 인용 된 표현식에서 호이 스팅 된 변수를 직접 처리 할 수 ​​없으므로 방문자가 사전을 사용하여 인덱스로 작업하도록 다시 작성합니다.

다른 표현식을 만드는 것뿐만 아니라 ExpressionVisitor도 다른 결과를 생성 할 수 있습니다. 다시 System.Linq.Expressions에는 디버그 문자열과 함께 내부 예제가 자체적으로 포함되어 있고 문제의 식을 방문하여 많은 식 유형에 대해 ToString()이 작동합니다.

이것은 데이터베이스 쿼리 linq 공급자가 식을 SQL 쿼리로 바꾸는 데 사용하는 방법 일 수 있습니다 (그래도 될 필요는 없지만).

언제 내가 그 중 어떤 것을 사용해야하는지 어떻게 알 수 있습니까?

이러한 방법의 기본 구현은 것입니다 : 표현이 자식 표현이없는 경우

  1. (예를 들어 Expression.Constant()의 결과) 그때는 다시 노드를 반환합니다.
  2. 그렇지 않으면 모든 하위 식을 방문한 다음 해당 식에 Update을 호출하고 결과를 다시 전달합니다. Update은 새로운 자식이있는 동일한 유형의 새 노드를 차례로 반환하거나 자식 노드가 변경되지 않은 경우 동일한 노드를 다시 반환합니다.

이와 같이, 용도에 관계없이 노드를 명시 적으로 조작해야한다는 것을 모를 경우 변경하지 않아도됩니다. 또한 부분 변경을 위해 노드의 새 버전을 얻는 편리한 방법은 Update입니다. 그러나 "당신의 목적이 무엇이든"은 물론 유스 케이스에 달려 있음을 의미합니다. 가장 일반적인 경우는 하나 또는 두 개의 표현식 유형이 무시를 필요로하거나, 모두 또는 거의 모두가 필요로하는 것과 같이 하나의 극단 또는 다른 것으로 갈 것입니다. 당신은 그것의 단계와 변수 또는 캐치 블록에 대한 TryExpression 모두에게 ReadOnlyCollection 같은 BlockExpression에서 아이들이 그 노드의 자식을 검사하는 경우

(한 가지주의해야 할 점은, 당신은 단지 때때로 다음의 경우 그 아이가 변경됩니다 당신은 자신을 결함으로 확인하는 것이 가장 좋습니다. [최근에 수정되었지만 아직 출시 된 버전이 아닙니다] 동일한 자식을 원래 ReadOnlyCollection과 다른 컬렉션에 Update에게 전달하면 새로운 표현식 불필요하게 만들어져 나무 위로 올라가는 효과가 있습니다. 일반적으로 무해하지만 시간과 기억이 낭비됩니다.

4

데이터베이스에 0 또는 1 (숫자)이 포함 된 필드가 있었고 응용 프로그램에서 bools를 사용하려고했습니다.

해결 방법은 0 또는 1을 포함하고 bool로 변환 된 "플래그"개체를 만드는 것입니다. 우리는 모든 응용 프로그램에서 bool처럼 사용했지만, .Where() 절에서 사용한 경우 EntityFramework는 변환 메서드를 호출 할 수 없다는 불평을했습니다.

우리는 expression 방문객을 사용하여 트리를 EF로 보내기 직전 .Where (x => x.Property)와 같은 모든 속성 액세스를 .Where (x => x.Property.Value == 1)로 변경했습니다.

+0

이것은 실제로 정말 멋진 예제입니다. – t3chb0t

2

ExpressionVisitorExpression '에 대해 visitor pattern을 사용합니다.

개념적 문제는 당신이 Expression 트리를 탐색 할 때, 당신이 알고있는 모든 주어진 노드가 Expression 있다는 것을,하지만 당신은 Expression의 구체적으로 어떤 종류를 알 수 없습니다. 이 패턴을 사용하면 작업중인 Expression의 종류를 알 수 있고 여러 종류의 유형별 처리를 지정할 수 있습니다.

Expression이있는 경우 .Modify으로 전화하면됩니다. Expression은 자체 유형을 알고 있으므로 적절한 override을 다시 호출합니다.

MSDN example you linked 보면 :이 예에서

public class AndAlsoModifier : ExpressionVisitor 
{ 
    public Expression Modify(Expression expression) 
    { 
     return Visit(expression); 
    } 

    protected override Expression VisitBinary(BinaryExpression b) 
    { 
     if (b.NodeType == ExpressionType.AndAlso) 
     { 
      Expression left = this.Visit(b.Left); 
      Expression right = this.Visit(b.Right); 

      // Make this binary expression an OrElse operation instead of an AndAlso operation. 
      return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method); 
     } 

     return base.VisitBinary(b); 
    } 
} 

ExpressionBinaryExpression 될 일이 있다면, 그것은 다시 예에 주어진 VisitBinary(BinaryExpression b)를 호출 할 수 있습니다. 이제 BinaryExpression임을 알고 BinaryExpression을 처리 할 수 ​​있습니다. 다른 종류의 Expression을 처리하는 다른 override 메서드를 지정할 수도 있습니다.

이것은 과부하 된 해상도 트릭이기 때문에 방문한 Expression 님이 가장 적합한 방법을 요청합니다. 따라서 다른 종류의 BinaryExpression이 있다면 하나의 특정 하위 유형에 override을 쓸 수 있습니다. 다른 하위 유형이 콜백하면 기본 처리 BinaryExpression 만 사용됩니다.

요약하면이 패턴을 사용하면 어떤 종류의 Expression이 작업 중인지 알고있어 Expression 트리를 탐색 할 수 있습니다.

+0

아, 나는 그것을 방문자 패턴과 전혀 관련시키지 않았습니다. 나는 당신의 설명을 좋아하고 다른 예를 통해 나는 그 생각을 시작한다. 나는 실제로 어떤 노드에서 내가 멈추어야 하는지를 알기 위해 조작하고자하는 표현 트리를 알아야하고, 필요하다면 그것을보고 작동하고 작동시키는 데 필요한 방식으로 수정한다. – t3chb0t

+1

@ t3chb0t 올바른 도구를 찾은 것 같습니다. 이 혜택을 얻을 수있는 다른 방법이 있습니다 (이중 디스패치 (https://en.wikipedia.org/wiki/Double_dispatch#Double_dispatch_in_C.23)라고도 함). 방문자 패턴은 하고있어. – Nat

관련 문제