Исключение при построении запроса LINQ с несколькими уровнями делегатов или лямбд («Значение не может быть нулевым. Имя параметра: экземпляр») - PullRequest
0 голосов
/ 22 июня 2011

EDIT

Я получаю исключение каждый раз, когда пытаюсь создать выражение LINQ из метода, который содержит вызов локально определенного делегата (или лямбды) или функции в классе или модуле.

У меня есть функция, которая принимает два параметра (делегат и целое число), а затем создает LINQ MethodCallExpression, который она использует для получения результатов:

Public Delegate Function CompareTwoIntegerFunction(ByVal i1 As Integer, ByVal i2 As Integer) As Boolean
Public Function Test(ByVal pFunc As CompareTwoIntegerFunction, ByVal i1 As Integer, ByVal i2 As Integer) As Boolean
    Dim lParamExpression As ParameterExpression = Expression.Parameter(GetType(Integer), "i")
    Dim lConstExpr As ConstantExpression = Expression.Constant(i1, GetType(Integer))
    Dim lMatcher As CompareTwoIntegerFunction = pFunc

    ' *** This line throws the exception (line 17)
    Dim lMatcherExpr As MethodCallExpression = Expression.Call(lMatcher.Method, lParamExpression, lConstExpr)

    ' Now use the expression and get the result
    Dim lFunc As Func(Of Integer, Boolean) = (Expression.Lambda(Of Func(Of Integer, Boolean))(lMatcherExpr, lParamExpression)).Compile

    Return lFunc(i2)
End Function

Я могу запустить этот код без ошибок в этом тесте:

    Dim lMatchedPass1 As Boolean = Test(Function(a, b) a = b, 10, 10)

Я даже могу придумать и сделать что-то вроде этого:

    Dim lMatchedPass2 As Boolean = Test(Function(a, b) (Function(c As Integer) c + 1)(a) = b, 10, 9)

Однако, если я попробую что-то подобное, я получу исключение:

    Dim ChildFunc As Func(Of Integer, Integer) = Function(s As Integer) s + 1
    Dim MatchedFail1 As Boolean = Test(Function(a, b) (ChildFunc(a)) = b, 10, 9)

Сообщение об исключении:

Exception: [2011 Jun 23 (Thu) 10:25:29 AM] [ System.Core ] 
Value cannot be null.
Parameter name: instance
   at System.Linq.Expressions.Expression.ValidateCallArgs(Expression instance, MethodInfo method, ReadOnlyCollection`1& arguments)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression[] arguments)
   at DataManager`1.Test(CompareTwoIntegerFunction pFunc, Int32 i1, Int32 i2) in DataManager.vb:line 17
   at DataManager`1..ctor() in DataManager.vb:line 27
Called from: Void ValidateCallArgs(System.Linq.Expressions.Expression, System.Reflection.MethodInfo, System.Collections.ObjectModel.ReadOnlyCollection`1[System.Linq.Expressions.Expression] ByRef)

Я хочу понять, почему MatchTest2 нормально, но MatchFail1 - это проблема.

Примечание 1: В приведенном выше примере используются константа и параметр, но я получаю одинаковые результаты с двумя константами или двумя параметрами. Я также могу получить исключение, заменив именованный тип делегата на определение анонимной функции.

Примечание 2: Я безуспешно пытался добавить Nothing в качестве параметра экземпляра в строку Expression.Call, например:

Dim lMatcherExpr As MethodCallExpression = Expression.Call(Nothing, pFunc.Method, lParamExpression, lConstExpr)

Примечание 3: Я обнаружил, что могу «исправить» проблему в приведенном выше коде, переместив мою «дочернюю функцию» в модуль или превратив ее в «разделяемую» функцию в моем классе. Это не очень реалистичное решение, хотя в нетривиальном случае, потому что я хочу иметь возможность передавать дочернюю функцию в качестве параметра.

Другими словами, моя цель - сделать что-то вроде этого:

Public Function RunTestWithChildFunction(ByVal a As Integer, ByVal b As Integer, ByVal ChildFunc As Func(Of Integer, Integer, Boolean)) As Boolean
    Return Test(Function(x, y) ChildFunc(x, y), a, b)
End Function
Public Function EqualityCheck(ByVal a As Integer, ByVal b As Integer) As Boolean
    Return a = b
End Function
Public Sub DoTest
    Dim lMatchedFail2 As Boolean = RunTestWithChildFunction(10, 10, AddressOf EqualityCheck)
End 

Добавление «shared» в EqualityCheck в этом коде не решает проблему, потому что адрес передается ByVal в RunCheckWithChildFunction (я получаю ошибку компиляции, если я пытаюсь передать его ByRef).

1 Ответ

0 голосов
/ 28 июня 2011

Кажется, что я могу обойти проблему, введя промежуточный класс. Экземпляр класса содержит адрес для «внутренних» лямбда-функций.

Итак, вместо этого:

    Dim lMatchedPass2 As Boolean = Test(Function(a As Integer, b As Integer) (Function(c As Integer) c + 1)(a) = (b + 1), 10, 10)

    Dim ChildFunc As Func(Of Integer, Integer) = Function(s As Integer) s + 1
    Dim MatchedFail1 As Boolean = Test(Function(a, b) (ChildFunc(a)) = b, 10, 9)

Мне бы звонили вот так:

    Dim lDoCompare As New DoCompare(Of Integer)(10)
    lDoCompare.Comparator = Function(a As Integer, b As Integer) (Function(c As Integer) c + 1)(a) = (b + 1)
    Dim lMatchClassPass1 as Boolean = TestClass(lDoCompare, 10)

    Dim ChildFunc As Func(Of Integer, Integer) = Function(c As Integer) c + 1
    lDoCompare.Comparator = Function(a As Integer, b As Integer) ChildFunc(a) = (b + 1)
    Dim lMatchClassPass2 as Boolean = TestClass(lDoCompare, 10)

Где новые классы выглядят так:

Public Function TestClass(Of T)(ByVal pComparator As IDoCompare(Of T), ByVal pValue As T) As Boolean
    Dim lParamExpression As ParameterExpression = Expression.Parameter(GetType(T), "t")
    Dim lCompareFunc As IDoCompare(Of T).Compare = AddressOf pComparator.DoCompare
    Dim lInstance As ConstantExpression = Expression.Constant(pComparator)
    Dim lMatcherExpr As MethodCallExpression = Expression.Call(lInstance, lCompareFunc.Method, lParamExpression)

    Dim lFunc As Func(Of T, Boolean) = (Expression.Lambda(Of Func(Of T, Boolean))(lMatcherExpr, lParamExpression)).Compile
    Return lFunc(pValue)
End Function

Interface IDoCompare(Of T)
    Delegate Function Compare(ByVal pParam As T) As Boolean
    Function DoCompare(ByVal pParam As T) As Boolean
End Interface

Class DoCompare(Of T) : Implements IDoCompare(Of T)
    Protected mClue As T
    Sub New(ByVal pClue As T)
        mClue = pClue
    End Sub
    Public Comparator As Func(Of T, T, Boolean) = Function(a As T, B As T) a.ToString = B.ToString
    Public Function DoCompare(ByVal pParam As T) As Boolean Implements IDoCompare(Of T).DoCompare
        Return Comparator(mClue, pParam)
    End Function
End Class
...