تجميع مفوض مع Expression.lambda () - المعلمة خارج النطاق، ولكن هل حقا؟
-
16-09-2019 - |
سؤال
صادفت مشكلة مثيرة للاهتمام اليوم أثناء تنفيذ ميزة في مكتبة بناء تعبير ديناميكية. وبشكل أكثر تحديدا، ولكن بشكل غير صحيح، هناك ميزة لتحديد أسبقية المشغل في تعبير.
عندما كان محرك LINQ هو تجميع التعبير النهائي، كنت مواجه InvalidOperationException
إعلان Lambda parameter out of scope
.
المشكلة تتجلى نفسها بعد تعيين ذات الصلة ParameterExpression
أشياء.
العمل مع شجرة التعبير Lambda كاملة وشكل بشكل جيد، اكتشفت أن إعادة تعيين Lambda ParameterExpression
كانت الكائنات المراجع الصحيحة غير صالحة عند تجميع LambDA.
هذا وصف قصير للسلوك الذي استخدمته في البداية قبل تطبيق الإصلاح:
- بناء شجرة التعبير، الموجهة للاستخدام مع
Queryable.Where
, ، التعبير الجذر يجريLambdaExpression
, ، شيدت باستخدامExpression.Lambda(التعبير, Expression.Parameter(GetType(اكتب), "اسم"))
- تفضل بزيارة شجرة التعبير (باستخدام LinQKit)، وبناء جدول تجزئة معلمات مواجهتها
- يتم استبدال المعلمات اللاحقة من نفس الاسم مع المعلمة الأولى التي واجهتها الاسم المتطابق
كانت النتيجة شجرة تعبير حيث كل ParameterExpression
مراجع نفس الاسم كانت مشيرا إلى نفس الكائن - ولكن InvalidOperationException
واجهها عند تجميع.
الإصلاح الذي تقدمت به المستخدم السلوك التالي:
- بناء المعلمات كصفيف
ParameterExpression
- بناء جذر Lambda، باستخدام
Expression.Lambda(التعبير, Parameterarray.)
- قم بزيارة شجرة التعبير (باستخدام LinQKIT)، واجهت المعلمات البديلة مع المعلمات من
Parameterarray.
تؤثر النتيجة النهائية بشكل جيد، على الرغم من أن هيكل التعبير Lambda هو نفسه من الناحية النظرية كما الإخراج من السلوك السابق.
السؤال هو: لماذا الفشل الأول، والنجاح الثاني?
فيما يلي فئة لاعبا اساسيا اختبار لإعادة إنتاج (عذر VB)، مع حالات الاختبار واثنين من الفئات الداعمة (يعتمد على nunit، linqkit):
ملاحظة: تمثيل TestFixtture و Test Test Attembute مفقودة - كيفية القيام به في علامة تخفيض ؟؟؟
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
المحلول
لا تفكر أشجار التعبير AFAIK كائنتين في ParametRExpression تم إنشاؤها باستخدام حجج متطابقة ك "نفس المعلمة".
دون اختبار التعليمات البرمجية الخاصة بك، فهذا ما العصي: كما قرأت السيناريو الأول (الفاش)، يمكنك استبدال جميع المعلمات نفسها مع أول من هذا القبيل واجهتها، ولكن هذه المعلمة التي واجهتها لأول مرة ليس هو نفس كائن parameterexpression كواحد تقوم بإنشائه في مكالمتك إلى Expression.lambda (). في السيناريو الثاني (النجاح)، هو.
تحريرها يجب أن أضيف أنني لم أستخدمه RENQKIT RepressionVisitor، ولكن بقدر ما أدرك أنه يعتمد على التعليمات البرمجية التي استخدمتها، حيث VisitlyLBDA غير قوي للغاية:
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 هذا، فسيكون ذلك نقطة الفشل.