동적 표현 건물 제작 라이브러리에 기능을 구현하면서 흥미로운 문제가 생겼습니다. 보다 구체적으로는 표현식에서 연산자 우선 순위를 정의하는 기능입니다.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)
- 루트 람다를 구축
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
실제로 성공적인 시나리오에서 람다 식 생성에 사용되는 매개 변수는 식의 모든 일치 매개 변수 elsehwere를 대체하는 데 사용되는 매개 변수이며 컴파일됩니다. 두 번째로 표현식 트리를 방문했을 때 첫 번째 시나리오의 매개 변수식이 모두 람다 매개 변수와 관련된 의도 된 참조를 가리키는 지 확인했습니다. 개념적으로 표현식 트리는 동일합니다. 람다식이 원래 매개 변수에 대한 내부 참조를 유지하는 경우가 거의 있습니다. – Rabid
위의 사용법에서 LinqKit의 ExpressionVisitor는 본문에서 참조 된 매개 변수뿐만 아니라 람다 매개 변수를 올바르게 방문하고 대체합니다. –
사실 - 리플렉션에서 - 나는 그것을 확인하지 못했습니다. 그러나 나는 그런 경우에 확장에 의한 행동을 정정하고 다시 시도한다. 나는 내일 다시보고 할 것이다 :) – Rabid