从操作代理获取实际参数类型

时间:2017-05-10 16:44:26

标签: .net vb.net delegates addressof

上下文

这是一个非常具体的问题。总而言之,我有一个让代表队列排队的课程。我知道每个方法只有一个参数,所以我将它们存储在Action(Object)列表中。我不能使用泛型,因为不同的方法可以有不同类型的参数,所以我改用Object。

当委托执行的时间时,用户传递一个参数,然后该参数将传递给委托方法。问题在于类型检查:如果他们提供的参数类型与代表期望的类型不匹配,我不会抛出特定的异常。

例如,如果将此方法传递给我的类:

Sub Test(SomeNumber As Integer)

然后尝试运行:

MyClass.ExecuteDelegate("DelegateArgument")

我希望能够抛出一个异常,说他们试图将String用作Integer并包含一些自定义信息。麻烦的是我无法找到一种方法。

问题

由于我将代理存储为Action( Object ),因此我不知道参数的实际类型是什么。我没有找到任何方法来查找此信息,因此我无法将参数的类型与用户提供的参数的类型进行比较。当我使用Action(Object).Method.GetParameters时,它只返回Object

或者,当我尝试调用Action(Object).Invoke()时,我尝试使用Try-Catch块检查InvalidCastException但是在委托方法中也捕获了任何InvalidCastException,我不想要

有没有办法实现我想要做的事情?

3 个答案:

答案 0 :(得分:0)

引入一个包含方法信息的类,并将其用于存储委托。

Public Class MethodData
    Public ReadOnly Property ArgumentType As Type
    Public ReadOnly Property Method As Action(Of Object)

    Public Sub New(argumentType As Type, method As Action(Of Object))
        ArgumentType = argumentType
        Method = method
    End Sub
End Class

Public Sub MethodWithInteger(argument As Integer)

End Sub

Public Sub MethodWithString(argument As String)

End Sub

Dim myDelegates = New List(Of MethodData) From
{
    New MethodData(GetType(Integer), AddressOf MethodWithInteger),
    New MethodData(GetType(String), AddressOf MethodWithString)
}

'execute method
Dim inputArgument = "notInteger"
Dim methodWithInteger = myDelegates(0)
If methodData.ArgumentType Is inputArgument.GetType() Then
    methodData.Method.Invoke(inputArgument)
End If

您可以将执行逻辑放在类MethodData中。

Public Class MethodData
    Public ReadOnly Property ArgumentType As Type
    Public ReadOnly Property Method As Action(Of Object)

    Public Sub New(argumentType As Type, method As Action(Of Object))
        ArgumentType = argumentType
        Method = method
    End Sub

    Public Sub Execute(input As Object)
        If ArgumentType Is input.GetType() Then
            Method.Invoke(input)
        End If
    End Sub
End Class

请注意,上面的代码(显然也是您的代码)只有在Option Strict设置为Off时才会编译。如果您将其设置为On,您将被迫将方法强制转换或包装到Action(Of Object)

答案 1 :(得分:0)

我不同意你不能使用泛型。在下面显示的示例中,我将队列定义为字典以允许基于ID进行检索。我这样做主要是因为我不明白你是如何设想检索一个给定的动作。

队列本身将Actions存储为委托,该类提供添加和运行Action的方法。 Action的参数类型信息通过Reflection检索。

Public Class DelegateQueue
    Private queue As New Dictionary(Of String, [Delegate])

    Public Sub AddMethod(Of T)(id As String, action As Action(Of T))
        queue.Add(id, action)
    End Sub

    Public Sub RunMethod(Of T)(id As String, arg As T)
        Dim meth As [Delegate] = Nothing
        If queue.TryGetValue(id, meth) Then
            Dim parameters As Reflection.ParameterInfo() = meth.Method.GetParameters
            Dim passedType As Type = GetType(T)
            If parameters.Length > 0 AndAlso parameters(0).ParameterType Is passedType Then
                Dim act As Action(Of T) = CType(meth, Global.System.Action(Of T))
                act.Invoke(arg)
            Else
                Throw New ArgumentException(String.Format("passed argument of type {0} to Action(Of {1}). Method ID {2}", passedType.Name, parameters(0).ParameterType.Name, id))
            End If
        Else
            Throw New ArgumentException("Method ID "" " & id & """ not found.")
        End If
    End Sub
End Class

使用示例:

Sub Demo()
    Dim q As New DelegateQueue
    q.AddMethod(Of Int32)("S1", AddressOf S1)
    q.AddMethod(Of String)("S2", AddressOf S2)

    q.RunMethod("S1", 23)
    q.RunMethod("S1", "fred") ' throws runtime error
End Sub

Private Sub S1(i As Int32)
End Sub

Private Sub S2(i As String)
End Sub

答案 2 :(得分:0)

我最终(经过很多谷歌搜索)找到另一个角度并拼凑出一个我满意的解决方案。

我偶然发现this,这本身并不是一个真正的答案(并且有很多我不能阅读的C#,更不用说翻译成VB了),但它确实给了我一个主意。以下是它的最终结果:

我原来的子"注册"我的班级的方法看起来(简化)像这样:

RegisterOperation(Routine As Action(Of Object))

它被称为这样:

RegisterOperation(AddressOf RoutineMethod)

我现在用这样的一个重载了这个子:

RegisterOperation(Routine As MethodInfo, Optional Instance As Object = Nothing)

它可以像这样调用:

RegisterOperation([GetType].GetMethod(NameOf(Routine))) 'For shared methods
RegisterOperation([GetType].GetMethod(NameOf(Routine)), Me) 'For instance methods

当然它并不像AddressOf那么漂亮,但它并不太糟糕,而且效果很好。

我将MethodInfo转换为这样的委托:

Dim ParamTypes = (From P In Routine.GetParameters() Select P.ParameterType)
Routine.CreateDelegate(Expression.GetDelegateType(ParamTypes.Concat({GetType(Void)}).ToArray), Instance)

这创建了一个委托,它保持所有原始参数数据的完整性,并允许用户注册方法而不必在多个位置声明参数类型,同时仍然允许我访问方法的数量和类型&#39 ; s参数,所以我可以验证它们。

此外,我必须对@TnTinMn表示我可以将我的代表存储为[Delegate],而不是将其硬编码为特定的Action(Of T)。这允许我将所有这些不同类型的代表存储在一起,并保留我原来的实现以实现向后兼容性。只要我确保参数编号和类型是正确的,一切都顺利进行。

修改

使用了一下之后,我找到了进一步简化它的方法。如果用户的方法是"注册"在调用RegisterOperation的类中声明,然后我可以使用StackTrace查找调用类型,如here所示。

我在另一个重载中实现了这个,所以用户只需要传递方法的名称,如下所示:

RegisterOperation(NameOf(Routine))

'To get the method from the name:
Dim Routine = (New StackTrace).GetFrame(1).GetMethod.ReflectedType.GetMethod(RoutineName)

这使它像AddressOf一样干净!