Отключение обработчиков событий

Обработчики событий, динамически назначаемые командой AddHandler, отключаются командой RemoveHandler, которой должны передаваться точно такие же аргументы, как и при соответствующем вызове AddHandlеr. Обычно для удаления динамически назначаемых обработчиков хорошо подходит метод Dispose. По этой причине в каждом классе, использующем динамическое назначение обработчиков, рекомендуется реализовать интерфейс IDisposable — это напомнит пользователям класса о необходимости вызова Dispose.

 

Обработка событий в иерархии наследования

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

Public Class ParentClass

Public Event ParentEventtByVal aThing As Object.

ByVal E As System.EventArgs)

' Программный код End Class

' Производный класс

Public Class ChildClass

Inherits ParentClass

Sub EventHandler(ByVal x As Integer)

Handles MyBase ParentEvent

'Обработка событий базового класса

End Sub

End Class

 

Делегаты

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

Механизм обратного вызова (а следовательно, и события) в VB .NET зависит от особой разновидности объектов .NET, называемых делегатами. Делегат является экземпляром класса System.Delegate. В простейшем случае в делегате инкапсулируется объект и адрес заданной функции или процедуры этого объекта. Такие делегаты идеально подходят для схем обратного вызова вроде той, что используется при обработке событий. Почему? Потому что делегат содержит всю информацию, необходимую для обратного вызова, и может использоваться для вызова нужного метода объекта-приемника.

Но прежде, чем переходить к описанию работы с делегатами, стоит подчеркнуть одно важное обстоятельство. Хотя обработка событий на платформе .NET основана на использовании делегатов, в подавляющем большинстве случаев вам не придется работать непосредственно с делегатами. Команда AddHandl ег предоставляет в ваше распоряжение все необходимое для гибкой обработки событий в VB .NET (впрочем, как вы вскоре увидите, у делегатов есть и другие применения).

В делегатах также могут инкапсулироваться общие методы класса без привязки ккон-кретному экземпляру. Кроме того, в групповых делегатах инкапсулируется сразу несколько объектов с несколькими процедурами.

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

Указатели на функции в VB6 . При вызовах функций API часто передается адрес функции для обратного вызова, поэтому в VB6 поддерживался оператор AddressOf. В VB6 адрес функции мог передаваться при любом вызове API. Но что происходило, если список параметров функции, адрес которой передавался при вызове, отличался от предполагаемого? Обычно это приводило к общей ошибке защиты (GPF) и даже к фатальным сбоям с появлением синего экрана.

Таким образом, произвольные указатели на функции обладают принципиальным недостатком: компилятор не может проверить, что такой указатель относится к функции правильного типа. Делегаты представляют собой разновидность указателей на функции, безопасных по отношению к типам. Следуя принципу «доверяй, но проверяй», компилятор автоматически проверяет сигнатуру вызываемой функции — такой вариант работает гораздо надежнее.

 

Создание делегата

Начнем с создания простейшего делегата, инкапсулирующего объект и «указатель» на процедуру этого объекта. Как показано ниже, синтаксис создания объектов чуть сложнее синтаксиса, используемого при создании простых объектов. Прежде всего нам понадобится класс, содержащий процедуру с определенной сигнатурой:

Class ClassForStringSubDelegate

' Использовать конструктор по умолчанию

Public Sub TestSub(ByVal aString As String)

Console. WriteLine(aString SaString)

End Sub

End Class

Чтобы создать делегат для обратного вызова этой процедуры, необходимо сообщить компилятору об использовании делегата для процедуры с одним строковым параметром. Первый шаг этого сценария выполняется за пределами Sub Main следующей строкой:

Public Delegate Sub StringSubDelegate(ByVal

aString As String)

Обратите внимание: в этой строке мы не объявляем делегат, а определяем его. Компилятор VB .NET автоматически создает новый класс StringSubDel egate, производный от System . Delegate1.

Далее в процедуре Sub Main экземпляр класса делегата создается оператором AddressOf для адреса процедуры, имеющей правильную сигнатуру. VB .NET автоматически вычисляет объект по полному имени процедуры. Команда создания экземпляра выглядит так:

aDel egate = AddressOf test.TestSub

Компилятор VB .NET понимает, что делегат создается для объекта test. Также можно воспользоваться ключевым словом New, однако это делается редко, поскольку New неявно вызывается в первой форме:

aDelegate = New StringSubDelegate(AddressOf test.TestSub)

После того как делегат будет создан, инкапсулированная в нем процедура вызывается методом Invoke класса Delegate, как в следующем фрагменте:

Sub Main( )

Dim test As New ClassForStri ngSubDelegate()

Dim aDelegate As StringSubDelegate

aDelegate = AddressOf test.TestSub

aDelegate.Invoke( "Hello" )

Console. ReadLineb

End Sub

На самом деле использовать Invoke необязательно — достаточно передать делегату нужные параметры. VB .NET поймет команду aDelegate(" Hello"), которая выглядит значительно проще.

В этом нетрудно убедиться, просматривая полученный IL-код при помощи программы ILDASM.

Согласитесь, такой способ вывода в консольном окне строки «HelloHello» выглядит несколько необычно!

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

Module Modulel

Public Delegate Sub StringSubDelegate(ByVal aString As String)

Sub Main()

Dim test As New ClassForStringSubDelegate()

Dim aDelegate As StringSubDelegate

aDelegate - AddressOf test.TestMsgBox

aDelegate("Hello")

Console. ReadLine()

End Sub

Class ClassForStringSubDelegate

' Использовать конструктор по умолчанию

Public Sub TestSub(ByVal aString As String)

Console.WriteLine(aString SaString)

End Sub

Public Sub TestMsgBox(ByVal aString As String)

MsgBox(aString &aString)

End Sub

End Class End Module

Поскольку для делегата важна только сигнатура инкапсулированного метода, он легко «переключается» на другой метод. Потребовалось создать новую версию для вывода информации в окне отладки (вместо консоли и окна сообщения)? Достаточно внести несколько изменений в делегат и добавить в класс функцию, инкапсулируемую делегатом.

Важнейшая особенность делегатов заключается в том, что связывание с методом производится на стадии выполнения. Таким образом, делегаты в сочетании с явным или неявным вызовом метода Invoke по своим возможностям значительно превосходят функцию VB6 CallByName.