gpt4 book ai didi

linq - 使用 Expression.Lambda() 编译委托(delegate) - 参数超出范围,但真的如此吗?

转载 作者:行者123 更新时间:2023-12-04 07:05:54 24 4
gpt4 key购买 nike

我今天在动态表达式构建库中实现一个功能时遇到了一个有趣的问题。更具体地说,但无关紧要的是,在表达式中定义运算符优先级的功能。

当 LINQ 引擎编译最终表达式时,我遇到了 InvalidOperationException声明 Lambda parameter out of scope .

分配相关的 ParameterExpression 后问题就显现出来了。对象。

使用完整且格式良好的 lambda 表达式树,我发现重新分配 Lambda 的 ParameterExpression编译 Lambda 时,指向有效引用的对象无效。

这是我在应用修复之前最初采用的行为的简短描述:

  • 构建表达式树,用于 Queryable.Where ,根表达式是 LambdaExpression , 使用 Expression.Lambda(expression, Expression.Parameter(GetType(type), "name")) 构建
  • 访问表达式树(使用LinqKit),构建遇到参数的哈希表
  • 后续的同名参数被替换为遇到的第一个同名参数

  • 结果是一个表达式树,其中所有 ParameterExpression同名的引用都指向同一个对象,但 InvalidOperationException编译时遇到。

    我应用的修复采用了以下行为:
  • 将参数构建为 ParameterExpression 的数组
  • 使用 Expression.Lambda(expression, parameterArray) 构造根 Lambda
  • 访问表达式树(使用 LinqKit),将遇到的参数替换为来自 parameterArray 的参数

  • 最终结果编译得很好,即使 Lambda 表达式结构在概念上与前一种行为的输出相同。

    问题是: 为什么第一个失败,第二个成功 ?

    下面是一个要重现的测试夹具类(请原谅 vb),带有测试用例和几个支持类(取决于 nUnit、LinqKit):

    注意:缺少 TestFixture 和 Test 属性声明 - 如何在 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

    最佳答案

    AFAIK 表达式树不会将使用相同参数创建的两个 ParameterExpression 对象视为“相同参数”。

    那么,在没有测试您的代码的情况下,这就是突出的:当我阅读第一个(失败)场景时,您将所有同名参数替换为遇到的第一个此类参数,但第一个遇到的参数与第一个参数不是同一个 ParameterExpression 对象您在对 Expression.Lambda() 的调用中创建。在第二个(成功的)场景中,它是。

    已编辑 我应该补充一点,我没有使用过 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 没有改进这一点,那将是失败的地方。

    关于linq - 使用 Expression.Lambda() 编译委托(delegate) - 参数超出范围,但真的如此吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1139370/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com