2009-07-16 3 views
2

동적 표현 건물 제작 라이브러리에 기능을 구현하면서 흥미로운 문제가 생겼습니다. 보다 구체적으로는 표현식에서 연산자 우선 순위를 정의하는 기능입니다.Expression.Lambda()를 사용하여 대리인 컴파일 - 범위를 벗어난 매개 변수.

LINQ 엔진이 최종 표현식을 컴파일 할 때 InvalidOperationException이 (가) Lambda parameter out of scope으로 표시되었습니다.

이 문제는 관련 ParameterExpression 개체를 할당 한 후에 나타납니다.

완전하고 잘 형성된 람다 식 트리로 작업하면서 람다를 컴파일 할 때 람다의 ParameterExpression 개체를 유효한 참조로 재 할당하는 것이 잘못되었음을 발견했습니다.

이 내가 수정을 적용하기 전에 내가 처음 채택 된 행동에 대한 간단한 설명입니다 : Queryable.Where

  • 는 식 트리를 구축, 사용을 목적을하는 LambdaExpression 인 루트 식, Expression.Lambda(expression, Expression.Parameter(GetType(type), "name"))
  • 을 사용하여 구성
  • LinqKit을 사용하여 표현식 트리를 방문하여 발생한 매개 변수의 해시 테이블을 작성하십시오.
  • 동일한 이름의 후속 매개 변수가 동일한 이름의 첫 번째 매개 변수로 대체됩니다.

결과는 동일한 이름의 모든 ParameterExpression 참조가 모두 동일한 객체를 가리키는 표현 트리 였지만 컴파일 할 때 InvalidOperationException이 발생했습니다.

나는 다음과 같은 동작 고용 적용된 수정 : Expression.Lambda(expression, parameterArray)

  • 방문 식 트리 (사용 LinqKit)를 사용하여

    • 루트 람다를 구축 ParameterExpression
    • 의 배열로 매개 변수를 빌드를, 대체 매개 변수가있는 매개 변수가 발생했습니다 parameterArray

    최종 결과는 람다 표현 구조 은 개념적으로 이전 동작의 출력과 동일한입니다.

    질문은 : 첫 번째 오류가 발생하고 두 번째 오류가 발생하는 이유는 무엇입니까??

    참고

    을 : TestFixture & 테스트 속성 선언이 누락 된 다음

    은 테스트 케이스와 (VB는 변명) 재현하는 테스트 픽스처 클래스와 지원 클래스의 커플 (NUNIT에 따라, LinqKit)입니다 - markdown에서하는 방법 ???

    
    
    Imports LinqKit 
    Imports NUnit.Framework 
    Imports System.Linq.Expressions 
    
    _ 
    Public Class ParameterOutOfScopeTests 
    
        Public Class TestObject 
         Public Name As String 
         Public DateOfBirth As DateTime = DateTime.Now 
         Public DateOfDeath As DateTime? 
        End Class 
    
        Public Class ParameterNormalisation 
         Inherits ExpressionVisitor 
    
         Public Sub New(ByVal expression As Expression) 
          _expression = expression 
         End Sub 
    
         Private _expression As expression 
         Private _parameter As ParameterExpression 
         Private _name As String 
    
         Public Function Normalise(ByVal parameter As ParameterExpression) As Expression 
          _parameter = parameter 
          _name = parameter.Name 
          _expression = Me.Visit(_expression) 
          Return _expression 
         End Function 
    
         Public Function Normalise(ByVal name As String) As Expression 
          _name = name 
          _expression = Me.Visit(_expression) 
          Return _expression 
         End Function 
    
         Protected Overrides Function VisitParameter(ByVal p As System.Linq.Expressions.ParameterExpression) As System.Linq.Expressions.Expression 
    
          Debug.WriteLine("ClientExpressionParameterNormalisation.VisitParameter:: Parameter visited: " & p.Name & " " & p.GetHashCode) 
          If p.Name.Equals(_name) Then 
    
           If _parameter Is Nothing Then 
            _parameter = p 
            Debug.WriteLine("ClientExpressionParameterNormalisation.VisitParameter:: Primary parameter identified: " & p.GetHashCode) 
           ElseIf Not p Is _parameter Then 
            Debug.WriteLine("ClientExpressionParameterNormalisation.VisitParameter:: Secondary parameter substituted: " & p.GetHashCode & " with " & _parameter.GetHashCode) 
            Return MyBase.VisitParameter(_parameter) 
           Else 
            Debug.WriteLine("ClientExpressionParameterNormalisation.VisitParameter:: Parameter already common: " & p.GetHashCode & " with " & _parameter.GetHashCode) 
           End If 
    
          End If 
    
          Return MyBase.VisitParameter(p) 
    
         End Function 
    
    
        End Class 
    
        _ 
        Public Sub Lambda_Parameter_Out_Of_Scope_As_Expected() 
    
         Dim treeOne As Expression(Of Func(Of TestObject, Boolean)) = Function(test As TestObject) test.DateOfBirth > Now And test.Name.Contains("name") 
         Dim treeTwo As Expression(Of Func(Of TestObject, Boolean)) = Function(test As TestObject) Not test.DateOfDeath.HasValue 
    
         Dim treeThree As Expression = Expression.And(treeOne.Body, treeTwo.Body) 
    
         Dim realParameter As ParameterExpression = Expression.Parameter(GetType(TestObject), "test") 
    
         Dim lambdaOne As LambdaExpression = Expression.Lambda(treeThree, realParameter) 
         Dim delegateOne As [Delegate] = lambdaOne.Compile 
    
        End Sub 
    
        _ 
        Public Sub Lambda_Compiles() 
    
         Dim treeOne As Expression(Of Func(Of TestObject, Boolean)) = Function(test As TestObject) test.DateOfBirth > Now And test.Name.Contains("name") 
         Dim treeTwo As Expression(Of Func(Of TestObject, Boolean)) = Function(test As TestObject) Not test.DateOfDeath.HasValue 
    
         Dim treeThree As Expression = Expression.And(treeOne.Body, treeTwo.Body) 
    
         Dim normaliser As New ParameterNormalisation(treeThree) 
         Dim realParameter As ParameterExpression = Expression.Parameter(GetType(TestObject), "test") 
         treeThree = normaliser.Normalise(realParameter) 
    
         Dim lambdaOne As LambdaExpression = Expression.Lambda(treeThree, realParameter) 
         Dim delegateOne As [Delegate] = lambdaOne.Compile 
    
        End Sub 
    
        _ 
        Public Sub Lambda_Fails_But_Is__Conceptually__Sound() 
    
         Dim treeOne As Expression(Of Func(Of TestObject, Boolean)) = Function(test As TestObject) test.DateOfBirth > Now And test.Name.Contains("name") 
         Dim treeTwo As Expression(Of Func(Of TestObject, Boolean)) = Function(test As TestObject) Not test.DateOfDeath.HasValue 
    
         Dim treeThree As Expression = Expression.And(treeOne.Body, treeTwo.Body) 
    
         Dim realParameter As ParameterExpression = Expression.Parameter(GetType(TestObject), "test") 
         Dim lambdaOne As LambdaExpression = Expression.Lambda(treeThree, realParameter) 
    
         Dim normaliser As New ParameterNormalisation(lambdaOne) 
         lambdaOne = DirectCast(normaliser.Normalise("test"), LambdaExpression) 
    
         Dim delegateOne As [Delegate] = lambdaOne.Compile 
    
        End Sub 
    
    End Class 
    
    
  • 답변

    3

    AFAIK 발현 나무 "같은 매개 변수"로 동일한 인수로 만든 두 개의 ParameterExpression 개체를 고려하지 않습니다.

    코드를 테스트하지 않은 상태에서 첫 번째 (실패한) 시나리오를 읽었을 때 같은 이름의 모든 매개 변수가 첫 번째로 발견되었지만 첫 번째로 발생한 매개 변수 은 동일하지 않습니다 ExpressionExample.Lambda()를 호출 할 때 생성 한 것과 같은 ParameterExpression 객체. 두 번째 (후속) 시나리오에서 그렇습니다.

    EDITED 내가 LinqKit의 ExpressionVisitor 사용하지 않은 것을 추가해야하지만 지금까지 내가이 VisitLambda 매우 강력하지 않은 내가 사용한 코드를 기반으로 알고 있어요 같이

    protected virtual Expression VisitLambda(LambdaExpression lambda) 
        { 
         Expression body = this.Visit(lambda.Body); 
         if (body != lambda.Body) 
         { 
          return Expression.Lambda(lambda.Type, body, lambda.Parameters); 
         } 
         return lambda; 
        } 
    

    식의 본문은 방문되지만 매개 변수는 방문하지 않습니다. LinqKit이 이것을 향상시키지 않았다면, 그것은 실패의 포인트가 될 것입니다.

    +0

    실제로 성공적인 시나리오에서 람다 식 생성에 사용되는 매개 변수는 식의 모든 일치 매개 변수 elsehwere를 대체하는 데 사용되는 매개 변수이며 컴파일됩니다. 두 번째로 표현식 트리를 방문했을 때 첫 번째 시나리오의 매개 변수식이 모두 람다 매개 변수와 관련된 의도 된 참조를 가리키는 지 확인했습니다. 개념적으로 표현식 트리는 동일합니다. 람다식이 원래 매개 변수에 대한 내부 참조를 유지하는 경우가 거의 있습니다. – Rabid

    +0

    위의 사용법에서 LinqKit의 ExpressionVisitor는 본문에서 참조 된 매개 변수뿐만 아니라 람다 매개 변수를 올바르게 방문하고 대체합니다. –

    +0

    사실 - 리플렉션에서 - 나는 그것을 확인하지 못했습니다. 그러나 나는 그런 경우에 확장에 의한 행동을 정정하고 다시 시도한다. 나는 내일 다시보고 할 것이다 :) – Rabid

    관련 문제