Я пытаюсь написать универсальную функцию, которая, учитывая ссылку на элемент управления / компонент и имя события, объявленного в его классе, должна иметь возможность извлекать (через Reflection ) все обработчики событий, зарегистрированные на данный момент для указанного имени события.
Первая и главная проблема, с которой я столкнулся (которая решена, поэтому вы можете игнорировать этот параграф), заключается в том, что все решения (в основном написанные на C #), которые я обнаружил в StackOverflow, ограничены в том смысле, что авторы ищут только объявление поля события в классе System.Windows.Forms.Control
, и по этой причине произойдет сбой, например, при попытке получить обработчики события System.Windows.Forms.ToolStripMenuItem.MouseEnter
(так как поле события объявлено в классе System.Windows.Forms.ToolStripItem
), а также не учитывает именование полей событий класса System.Windows.Forms.Form
с подчеркиванием.
Итак, я рассмотрел все это, и в настоящее время мое решение работает (или я думаю, что оно работает) для любого класса, который наследуется от System.ComponentModel.Component
.
Единственная проблема, с которой я сейчас сталкиваюсь - это когда я объявляю пользовательский тип (который наследуется от Control / UserControl / Компонент / Форма и т. Д.), И я передаю этот тип своей функции. В этом случае я получаю исключение с нулевой ссылкой. Не уверен, что я тут не так делаю ...
Public Shared Function GetEventHandlers(component As IComponent, eventName As String) As IReadOnlyCollection(Of [Delegate])
Dim componentType As Type
Dim declaringType As Type ' The type on which the event is declared.
Dim eventInfo As EventInfo
Dim eventField As FieldInfo = Nothing
Dim eventFieldValue As Object
Dim eventsProp As PropertyInfo
Dim eventsPropValue As EventHandlerList
Dim eventDelegate As [Delegate]
Dim invocationList As [Delegate]()
' Possible namings for an event field.
Dim eventFieldNames As String() =
{
$"Event{eventName}", ' Fields declared in 'System.Windows.Forms.Control' class.
$"EVENT_{eventName.ToUpper()}", ' Fields declared in 'System.Windows.Forms.Form' class.
$"{eventName}Event" ' Fields auto-generated.
}
Const bindingFlagsEventInfo As BindingFlags =
BindingFlags.ExactBinding Or
BindingFlags.Instance Or
BindingFlags.NonPublic Or
BindingFlags.Public Or
BindingFlags.Static
Const bindingFlagsEventField As BindingFlags =
BindingFlags.DeclaredOnly Or
BindingFlags.ExactBinding Or
BindingFlags.IgnoreCase Or
BindingFlags.Instance Or
BindingFlags.NonPublic Or
BindingFlags.Static
Const bindingFlagsEventsProp As BindingFlags =
BindingFlags.DeclaredOnly Or
BindingFlags.ExactBinding Or
BindingFlags.Instance Or
BindingFlags.NonPublic
Const bindingFlagsEventsPropValue As BindingFlags =
BindingFlags.Default
componentType = component.GetType()
eventInfo = componentType.GetEvent(eventName, bindingFlagsEventInfo)
If (eventInfo Is Nothing) Then
Throw New ArgumentException($"Event with name '{eventName}' not found in type '{componentType.FullName}'.", NameOf(eventName))
End If
declaringType = eventInfo.DeclaringType
For Each name As String In eventFieldNames
eventField = declaringType.GetField(name, bindingFlagsEventField)
If (eventField IsNot Nothing) Then
Exit For
End If
Next name
If (eventField Is Nothing) Then
Throw New ArgumentException($"Field with name 'Event{eventName}', 'EVENT_{eventName.ToUpper()}' or '{eventName}Event' not found in type '{declaringType.FullName}'.", NameOf(eventName))
End If
#If DEBUG Then
Debug.WriteLine($"Field with name '{eventField.Name}' found in type '{declaringType.FullName}'")
#End If
eventFieldValue = eventField.GetValue(component)
eventsProp = GetType(Component).GetProperty("Events", bindingFlagsEventsProp, Type.DefaultBinder, GetType(EventHandlerList), Type.EmptyTypes, Nothing)
eventsPropValue = DirectCast(eventsProp.GetValue(component, bindingFlagsEventsPropValue, Type.DefaultBinder, Nothing, CultureInfo.InvariantCulture), EventHandlerList)
eventDelegate = eventsPropValue.Item(eventFieldValue)
invocationList = eventDelegate.GetInvocationList()
If (invocationList Is Nothing) Then ' There is no event-handler registered for the specified event.
Return Enumerable.Empty(Of [Delegate]).ToList()
End If
Return invocationList
End Function
Исключение возникает в этой строке:
invocationList = eventDelegate.GetInvocationList()
потому что eventDelegate
равно нулю.
Чтобы проверить исключение, вы можете взять этот класс в качестве примера:
Public Class TestUserControl : Inherits UserControl
Event TestEvent As EventHandler(Of EventArgs)
Overridable Sub OnTestEvent(e As EventArgs)
If (Me.TestEventEvent IsNot Nothing) Then
RaiseEvent TestEvent(Me, e)
End If
End Sub
End Class
И пример использования, подобный этому:
Dim ctrl As New TestUserControl()
AddHandler ctrl.TestEvent, Sub()
Debug.WriteLine("Hello World!")
End Sub
Dim handlers As IReadOnlyCollection(Of [Delegate]) =
GetEventHandlers(ctrl, NameOf(TestUserControl.TestEvent))
For Each handler As [Delegate] In handlers
Console.WriteLine($"Method Name: {handler.Method.Name}")
Next
Не уверен, что, может быть, это проблема, связанная с флагами привязки, или, возможно, именование поля события ... но у меня нет этой проблемы с нулевой ссылкой при попытке выполнить то же самое с любым встроенным элементом управления / компонентом класс, который выставляет события, вместо этого TestUserControl
класс.
Что я делаю не так? И как мне это исправить ?. Обратите внимание, что эта функция все еще должна быть универсальной.