Ralf Meier Ответов: 1

Создайте делегат из methodinfo (2)


Моя ситуация заключается в том, что я хочу иметь делегат метода из элемента управления.

Для этого я использую следующий код :
Private RepeatDelayCounter As Integer = 0
Private RepeatControl As Control = Nothing
Private Delegate Sub delegate_OnClick(instance As Control, e As System.EventArgs)
Private RepeatDelegate As delegate_OnClick = Nothing


Private Sub Button_Down(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    RepeatControl = sender
    RepeatDelegate = Nothing

    Dim myType As Type = RepeatControl.GetType
    Dim myInfo As System.Reflection.MethodInfo = myType.GetMethod("OnClick", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
    If myInfo IsNot Nothing Then RepeatDelegate = [Delegate].CreateDelegate(GetType(delegate_OnClick), myInfo, False)


    RepeatDelayCounter = 0
End Sub

Вы должны знать, что это только часть (но соответствующая) сложного класса.

Этот код работает правильно (я получаю делегат), если элемент управления, запускающий Button_Down-метод, использует переопределенную версию OnClick-метода.

Если я использую не индивидуальный элемент управления, а "стандартный" элемент управления (например, кнопку) Я получаю правильный MethodInfo, потому что этот элемент управления имеет OnClick-метод внутри, но я не могу получить делегат.
Сообщение об ошибке, которое я получаю, указывает на флаги привязки, но они полностью совпадают с теми, что были получены из моего пользовательского элемента управления. Поэтому я думаю, что это происходит от защищенного атрибута метода OnClick из "стандартного"элемента управления.

Так... что мне нужно, так это полезный совет, как решить эту проблему-потому что я хочу ее понять ... ;-)

Что я уже пробовал:

см. выше ...

Tomas Takac

Вы создаете делегат для метода экземпляра, но не предоставляете instace. Найдено здесь: http://stackoverflow.com/questions/11120401/creating-delegate-from-methodinfo

Richard Deeming

Это откройте экземпляр делегата[^]- экземпляр передается в качестве первого параметра.

1 Ответов

Рейтинг:
4

Richard Deeming

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

Class Foo
    Protected Overridable Sub Click()
        Console.WriteLine("Foo.Click")
    End Sub
End Class

Class Bar : Inherits Foo
End Class

Class Baz : Inherits Foo
    Protected Overrides Sub Click()
        Console.WriteLine("Baz.Click")
        MyBase.Click()
    End Sub
End Class

Delegate Sub MyDelegate(ByVal instance As Foo)

Function CreateDelegate(ByVal instance As Foo) As MyDelegate
    Dim myType As Type = instance.GetType()
    Dim myInfo As MethodInfo = myType.GetMethod("Click", BindingFlags.NonPublic Or BindingFlags.Instance)
    Return DirectCast([Delegate].CreateDelegate(GetType(MyDelegate), myInfo, True), MyDelegate)
End Function

Sub Main
    Dim f As New Foo()
    CreateDelegate(f).Invoke(f) 
    ' OK: Prints "Foo.Click"
    
    f = New Bar()
    CreateDelegate(f).Invoke(f) 
    ' OK: Prints "Foo.Click"
    
    f = New Baz()
    CreateDelegate(f).Invoke(f) 
    ' ArgumentException: 
    ' Cannot bind to the target method because its signature or security 
    ' transparency is not compatible with that of the delegate type.
End Sub


Причина довольно проста. Когда метод был переопределен, GetMethod возвращает MethodInfo представление переопределенной версии метода. Эта переопределенная версия может быть вызвана только на экземпляре класса, в котором объявлено переопределение. Но вы пытаетесь привязать его к делегату, который может быть вызван какой-нибудь экземпляр базового класса.

Так, например, вы можете создать делегат для вызова Baz.Click, но проходят в экземпляре Bar вместо.

Простое решение состоит в том, чтобы получить MethodInfo для базового метода:
Function CreateDelegate(ByVal instance As Foo) As MyDelegate
    Dim myType As Type = GetType(Foo)
    Dim myInfo As MethodInfo = myType.GetMethod("Click", BindingFlags.NonPublic Or BindingFlags.Instance)
    Return DirectCast([Delegate].CreateDelegate(GetType(MyDelegate), myInfo, True), MyDelegate)
End Function

Вызов делегата все равно вызовет переопределенную версию:
f = New Baz()
CreateDelegate(f).Invoke(f) 
' OK: Prints "Baz.Click", then "Foo.Click"


Таким образом, ваш код станет:
Private Sub Button_Down(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    RepeatControl = sender
    RepeatDelegate = Nothing
    
    Dim myType As Type = GetType(Control)
    Dim myInfo As System.Reflection.MethodInfo = myType.GetMethod("OnClick", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
    If myInfo IsNot Nothing Then RepeatDelegate = [Delegate].CreateDelegate(GetType(delegate_OnClick), myInfo, False)
    
    RepeatDelayCounter = 0
End Sub


Кроме того, вы можете создать закрытый делегат экземпляра, и вам не понадобится RepeatControl член вообще:
Private RepeatDelayCounter As Integer = 0
Private Delegate Sub delegate_OnClick(ByVal e As System.EventArgs)
Private RepeatDelegate As delegate_OnClick = Nothing

Private Sub Button_Down(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    RepeatDelegate = Nothing
    
    Dim myType As Type = GetType(Control)
    Dim myInfo As System.Reflection.MethodInfo = myType.GetMethod("OnClick", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
    If myInfo IsNot Nothing Then RepeatDelegate = [Delegate].CreateDelegate(GetType(delegate_OnClick), sender, myInfo, False)

    RepeatDelayCounter = 0
End Sub


Ralf Meier

Привет Ричард,
еще раз спасибо за Вашу (очень большую) работу и время, которое вы потратили на мою проблему.
Кроме того, ваше альтернативное предложение очень хорошо - спасибо и за это тоже.

Ralf Meier

Привет Ричард,
в соответствии с вашим первым предложением вашего решения :
То, что я написал, было неверно - разница для моего предоставленного кода заключалась в базовом типе. Элементы управления, которые я разработал, происходят от контроля. Итак, код сработал. Кнопка, которую я использовал позже, произошла от Buttonbase. Я думаю, что это было важным отличием - но это решается с помощью вашего изменения (Dim myType As Type = GetType(Control)).
Так что еще раз-большое спасибо ...