Получить фактические типы параметров из делегата действия

контекст

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

Когда приходит время выполнить делегат, пользователь передает аргумент, который затем передается методу делегата. Проблема в проверке типов: я не хочу выдавать конкретное исключение, если тип предоставленного параметра не соответствует типу, который ожидает делегат.

Например, если передать этот метод в мой класс:

Sub Test(SomeNumber As Integer)

а затем попробуйте запустить:

MyClass.ExecuteDelegate("DelegateArgument")

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

проблема

Поскольку я храню делегаты как Action (Object), я понятия не имею, каков действительный тип параметра. Я не нашел способа найти эту информацию, поэтому не могу сравнить тип параметра с типом предоставленного пользователем аргумента. Когда я использую Action(Object).Method.GetParameters он только возвращает объект

В качестве альтернативы я попытался использовать блок Try-Catch для проверки исключения InvalidCastException при попытке вызвать Action(Object).Invoke() но это также ловит любое InvalidCastException в методе делегата, который я не хочу.

Есть ли способ добиться того, что я пытаюсь сделать?

3 ответа

Решение

В итоге (после большого количества поисков в Google) я нашел другой подход и нашел решение, которым я доволен.

Я наткнулся на это, что само по себе не было ответом (и у меня было много 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], а не жестко кодировать его как конкретное действие (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!

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

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

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

Сама очередь хранит действия в качестве делегата, а класс предоставляет методы для добавления и запуска действия. Информация о типе аргумента 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
Другие вопросы по тегам