1

语境

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

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

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

Sub Test(SomeNumber As Integer)

然后尝试运行:

MyClass.ExecuteDelegate("DelegateArgument")

我希望能够抛出某个异常,说他们试图将字符串用作整数并包含一些自定义信息。问题是我找不到这样做的方法。

问题

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

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

有什么方法可以实现我想要做的事情吗?

4

3 回答 3

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).

于 2017-05-10T19:08:39.727 回答
0

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

队列本身将动作存储为委托,并且该类提供了添加和运行动作的方法。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
于 2017-05-10T19:30:44.167 回答
0

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

我偶然发现了这个,它本身并不是一个真正的答案(并且有很多我无法阅读的 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)

这将创建一个委托,该委托保持所有原始参数数据不变,并允许用户注册方法而无需在多个位置声明参数类型,同时仍允许我访问方法参数的数量和类型,以便我可以验证他们。

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

编辑:

在使用了一点之后,我找到了一种进一步简化它的方法。如果用户“注册”的方法是在调用 RegisterOperation 的类中声明的,那么我可以使用 StackTrace 来查找调用类型,如此处所示

我在另一个重载中实现了这一点,因此用户只需像这样传递方法的名称:

RegisterOperation(NameOf(Routine))

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

这使它和 AddressOf 一样干净!

于 2017-05-15T14:00:06.580 回答