تجميع مفوض مع Expression.lambda () - المعلمة خارج النطاق، ولكن هل حقا؟

StackOverflow https://stackoverflow.com/questions/1139370

سؤال

صادفت مشكلة مثيرة للاهتمام اليوم أثناء تنفيذ ميزة في مكتبة بناء تعبير ديناميكية. وبشكل أكثر تحديدا، ولكن بشكل غير صحيح، هناك ميزة لتحديد أسبقية المشغل في تعبير.

عندما كان محرك 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 هذا، فسيكون ذلك نقطة الفشل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top