Групповые
делегаты
В приведенных
выше примерах в делегате инкапсулировался адрес одной функции или процедуры.
Нередко в делегатах требуется инкапсулировать сразу несколько процедур (инкапсуляция
нескольких функций особого смысла не имеет — каким должно быть возвращаемое
значение?). Подобные делегаты называются групповыми (multicast) и реализуются
в виде делегата, содержащего несколько однотипных делегатов. При наличии группового
делегата все инкапсулированные процедуры вызываются одним методом Invoke, причем
это происходит в соответствии с порядком занесения их делегатов в групповой
делегат.
Чтобы создать
групповой делегат, следует объединить минимум двух делегатов одного типа и присвоить
результат переменной того же типа. Задача решается статическим методом Combine
класса System.Delegate, который возвращает новый делегат.
Допустим, firstDel и secDel — экземпляры класса MyMultiCastDelegate. Следующая команда объединяет firstDel и secDel в групповой делегат, хранящийся в
firstDel: firstDel
=System.Delegate.Combine(firstDel,secDel)
Ниже приведено
простое приложение, объединяющее адреса нескольких функций в групповом делегате:
1 Option Strict
On
2 Module Modulel
3 Sub Main()
4 Console.WriteLine("Calling
delegate function...")
5 RegisterDelegate(AddressOf
CallBackHandlerl)
6 RegisterDelegate(AddressOf
CallBackHandler2)
7 Call Delegates
()
8 Console.WriteLine(
9 "Finished
calling.delegate function...")
10 Console.ReadLine()
11 End Sub
12 Public Sub
CallBackHandlerHByVal lngVal As RETURNJALUES)
13 Console.WriteLine("Callback
1 returned " & IngVal)
14 End Sub
15 Public Sub
CallBackHandler2(ByVallngVal As RETURNJALUES)
16 Console.WriteLine("Callback
2 returned " & IngVal)
17 End Sub
18 End Module
19 Module Module2
20 Public Delegate
Sub CallBackFunc(ByVallngValAs RETURN_VALUES)
21 Private m_cbFunc
As CallBackFunc
22 Public Enum
RETURN_VALUES
23 VALUE_SUCCESS
24 VALUE_FAILURE
25 End Enum
26 Public Sub
RegisterDelegate(ByRef cbFunc As CallBackFunc)
27 m_cbFunc
= CType(System.Delegate.Combine(_
28 m_cbFunc.cbFunc).CallBackFunc)
29 End Sub
30 Public Sub
Call Delegates ()
31 Dim IngCounter
As Long = 0
32 ' Вызвать
процедуры через делегата
33 ' и вернуть
признак успешного вызова
34 m_cbFunc(RETURN
VALUES.VALUE_SUCCESS)
35 End Sub
36 End Module
В строках 5 и 6 вызывается процедура модуля Module2 (строки 26-28), где и происходит фактическое построение группового делегата. Это возможно благодаря тому, что делегат передается по ссылке, а не по значению. Обратите внимание на преобразование типа метода Combine к типу делегата в строке 27. Непосредственный вызов функций группового делегата происходит в строках 30-35. Всем зарегистрированным функциям передается значение перечисляемого типа RETURNJALUES . VALUE_SUCCESS. Результат выполнения программы показан на рисунке.
Групповые
делегаты как члены классов
В предыдущем
примере все модули имеют доступ ко всем функциям остальных модулей. Такую архитектуру
нельзя признать удачной — правильнее было бы оформить делегат в виде члена класса,
нежели в виде открытого объекта. Это позволит выполнить перед его созданием
проверку, аналогичную той, которая выполняется для других членов класса. Ниже
приведен слегка измененный вариант предыду-
щей архитектуры,
где перед дополнением группового делегата новыми функциями выполняется проверка
(в данном примере — весьма тривиальная). Соответствующий фрагмент выделен жирным
шрифтом:
Option Strict
On
Public Class
DelegateServer
Public Delegate Sub ClientCallback(ByVal IngVal As Long)
Private m_Clients As ClientCallback
' Использовать конструктор по умолчанию
Public Sub RegisterDelegate(ByVal
aDelegate As
ClientCallback.ByVal dolt As Boolean)
' Обычно здесь выполняется полноценная проверка.
' В данном примере функция обратного вызова регистрируется
' лишь в том случае, если второй параметр равен
True. If dolt
Then
m_Clients =
CType(System.Delegate.Combine(m_ Clients.aDelegate)._
ClientCallback)
End If
End Sub
Public Sub CallClients(ByVal
IngVal As Long)
m_Clients( IngVal)
End Sub
End Class
Module Modulel
Sub Main()
Dim delsrv As
New DelegateServer()
delsrv.RegisterDelegate(AddressOf
DelegateCallbackHandlerl.True)
' He вызывается
- второй параметр равен False!
delsrv.RegisterDelegate(AddressOf
DelegateCal1backHandler2.False)
' Инициировать
обращение к клиентам
delsrv.CallClients(125)
Console.WriteLine("Press
enter to end.")
Console.ReadLine()
End Sub
Public Sub DelegateCallbackHandlerKByValIngVal
As Long)
System.Console.WriteLine("DelegateCa11backHandlerl cal1ed")
End Sub
Public Sub DelegateCallbackHandler2(ByVal
IngVal As Long)
System.Console.Wri teLine("DelegateCal1backHandler2 cal1ed")
End Sub
End Module
Мы рассмотрели
разнообразные примеры использования делегатов, однако ни один из них не имел
отношения к обработке событий. Впрочем, связь между делегатами и событиями в
VB .NET весьма проста. При каждом использовании сокращенного синтаксиса обработки
событий, описанного в первой половине главы, VB .NET незаметно определяет класс
делегата для обработки события, а команда AddressOf создает экземпляр делегата
для этого обработчика. Например, следующие две строки эквивалентны (EventHandler
— имя неявно определяемого делегата):
AddHandler Buttonl.Click.AddressOf
Me.Buttonl_Click
AddHandler Buttonl.Click.New
EventHandler(AddressOf Buttonl Click)
В сущности,
каждое событие соответствует делегату следующего вида:
Public Delegate
Event (sender As Object.evt As EventArgs)
Вызов RaiseEvent
просто приводит к вызову Invoke для автоматически сгенерированного делегата.