Наряду со свойствами и методами классы и интерфейсы могут иметь делегаты и события. Делегаты представляют такие объекты, которые указывают на другие методы. При этом делегаты и методы, на которые ссылаются делегаты, должны иметь те же параметры и тот же тип возвращаемого значения. Создадим два делегата:
Delegate Function Operation(x As Integer, y As Integer) As Integer Delegate Sub GetMessage()
Первый делегат у нас ссылается на функцию, которая в качестве параметров принимает два значения типа Integer и возвращает некоторое число.
Второй делегат у нас ссылается на процедуру без параметров. Чтобы использовать делегат, нам надо создать его объект с помощью конструктора, в который
мы передаем адрес метода, вызываемого делегатом. Чтобы вызвать делегат, надо использовать его метод Invoke
. Кроме того, делегаты могут выполняться
в асинхронном режиме, при этом нам не надо создавать второй поток, нам надо лишь вместо метода Invoke
использовать пару методов
BedinInvoke/EndInvoke.
Public Delegate Sub GetMessage() Sub Main() Dim del As GetMessage If Date.Now.Hour < 12 Then del = New GetMessage(AddressOf GoodMorning) Else del = New GetMessage(AddressOf GoodEvening) End If del.Invoke() Console.ReadLine() End Sub Sub GoodMorning() Console.WriteLine("Good Morning") End Sub Sub GoodEvening() Console.WriteLine("Good Evening") End Sub
В данном случае мы в зависимости от времени передаем в делегат адрес определенного метода (с помощью ключевого слова AddressOf и выводим сообщение.
Теперь посмотрим на примере другого делегата:
Delegate Function Operation(x As Integer, y As Integer) As Integer Sub Main() Dim op As New Operation(AddressOf Add) Console.WriteLine(op.Invoke(4, 5)) Console.ReadLine() End Sub Function Add(x As Integer, y As Integer) As Integer Return x + y End Function Function Multiply(x As Integer, y As Integer) As Integer Return x * y End Function
Так как второй делегат ссылается на функции с двумя параметрами, то при вызове делегата мы должны передать в метод Invoke два значения.
Как и любой объект, делегат можно использовать в качестве параметра метода:
Public Delegate Sub GetMessage() Sub Main() If Date.Now.Hour < 12 Then Show_Message(AddressOf GoodMorning) Else Show_Message(AddressOf GoodEvening) End If Console.ReadLine() End Sub Private Sub Show_Message(_del As GetMessage) _del.Invoke() End Sub Sub GoodMorning() Console.WriteLine("Good Morning") End Sub Sub GoodEvening() Console.WriteLine("Good Evening") End Sub
Однако, эти примеры не могут показать всю мощь делегатов, так как мы вполне спокойно могли обойтись и без них, вызвав напрямую методы. А наиболее сильная сторона делегатов состоит в том, что они служат в качестве методов обратного вызова, уведомляя другие объекты о произошедших событиях. Итак, вернемся к нашим классам, описывающим клиента банка, которые мы разработали в предыдущих главах (в данном случае классы Employee и Manager опущены, так как они нам не понадобятся):
Public MustInherit Class Person Public Property FirstName() As String Public Property LastName() As String Public MustOverride Sub Display() Public Sub New(fName As String, lName As String) FirstName = fName LastName = lName End Sub End Class Public Class Client Inherits Person Implements IAccount 'Переменная для хранения суммы Dim _sum As Integer 'Переменная для хранения процента Dim _procentage As Integer Public Property Bank As String 'Текущая сумма на счете ReadOnly Property CurentSum() As Integer Implements IAccount.CurentSum Get Return _sum End Get End Property 'Метод для добавления денег на счет Sub Put(sum As Integer) Implements IAccount.Put _sum += sum End Sub 'Метод для снятия денег со счета Sub Withdraw(sum As Integer) Implements IAccount.Withdraw If sum <= CurentSum Then _sum -= sum End If End Sub 'Процент начислений ReadOnly Property Procentage() As Integer Implements IAccount.Procentage Get Return _procentage End Get End Property Public Overrides Sub Display() Console.WriteLine(FirstName & " " & LastName & " has an account in bank " & Bank) End Sub Public Sub New(fName As String, lName As String, _bank As String, _sum As Integer) MyBase.New(fName, lName) Bank = _bank Me._sum = _sum End Sub End Class Public Interface IAccount ReadOnly Property CurentSum() As Integer Sub Put(sum As Integer) Sub Withdraw(sum As Integer) ReadOnly Property Procentage() As Integer End Interface
Допустим, в случае вывода денег с помощью метода Withdraw
нам надо как-то уведомлять об этом самого клиента и, может быть,
другие объекты. Для этого создадим делегат AcoountStateHandler
. Чтобы использовать делегат, нам надо создать переменную этого делегата, а затем
присвоить ему метод, который будет вызываться делегатом. Итак, добавим в класс Client
следующие строки:
Public Class Client Inherits Person Implements IAccount 'Объявляем делегат Public Delegate Sub AccountStateHandler(message As String) 'Создаем переменную делегата Dim del As AccountStateHandler 'Регистрируем делегат Public Sub RegisterHandler(_del As AccountStateHandler) del = _del End Sub 'Здесь остальной код
Здесь все понятно. Сначала создаем делегат, который будет указывать на метод с параметром message типа String. Затем создаем переменную делегата. И в конце создаем метод, в котором будет происходить присваивание делегату ссылки на метод. Теперь изменим метод Withdraw следующим образом:
Sub Withdraw(sum As Integer) Implements IAccount.Withdraw If sum <= CurentSum Then _sum -= sum If del IsNot Nothing Then del("Сумма " & sum & " снята со счета") End If Else If del IsNot Nothing Then del("Недостаточно денег на счете") End If End If End Sub
Теперь в главной программе протестируем работу делегата:
Module Module1 Sub Main() 'Создаем нового клиента Dim client1 As New Client("John", "Thompson", "City Bank", 200) 'Добавляем в делегат ссылку на метод Show_Message client1.RegisterHandler(New Client.AccountStateHandler(AddressOf Show_Message)) 'Два раза подряд пытаемся снять деньги client1.Withdraw(100) client1.Withdraw(150) Console.ReadLine() End Sub Private Sub Show_Message(message As String) Console.WriteLine(message) End Sub End Module
Запустив программу, мы получим два разных сообщения, которые мы передали в коде класса Client:
Сумма 150 снята со счета Недостаточно денег на счете
Хотя в примере наш делегат принимал адрес на один метод, в действительности он может указывать сразу на несколько методов. Кроме того,
при необходимости мы можем удалить ссылки на адреса определенных методов, чтобы они не вызывались при вызове делегата. Итак, изменим в классе
Client
метод RegisterHandler и добавим новый метод UnregisterHandler, который будет удалять методы из списка методов делегата:
Public Sub RegisterHandler(_del As AccountStateHandler) Dim mainDel As [Delegate] = System.Delegate.Combine(_del, del) del = CType(mainDel, AccountStateHandler) End Sub Public Sub UnregisterHandler(_del As AccountStateHandler) Dim mainDel As [Delegate] = System.Delegate.Remove(del, _del) del = CType(mainDel, AccountStateHandler) End Sub
В первом методе метод Combine объединяет делегаты _del
и del
в один, который потом присваивается
переменной del
. Во втором методе метод Remove возвращает делегат, из списка вызовов которого удален делегат _del
.
Теперь перейдем к основной программе:
Sub Main() Dim client1 As New Client("John", "Thompson", "City Bank", 200) client1.RegisterHandler(New Client.AccountStateHandler(AddressOf Show_Message)) client1.RegisterHandler(New Client.AccountStateHandler(AddressOf Color_Message)) client1.Withdraw(100) client1.Withdraw(150) 'Удаляем делегат client1.UnregisterHandler(New Client.AccountStateHandler(AddressOf Color_Message)) client1.Withdraw(50) Console.ReadLine() End Sub Private Sub Show_Message(message As String) Console.WriteLine(message) End Sub Private Sub Color_Message(message As String) 'Установаливаем красный цвет символов Console.ForegroundColor = ConsoleColor.Red Console.WriteLine(message) 'Сбрасываем настрйоки цвета Console.ResetColor() End Sub
В целях тестирования мы создали еще один метод - Color_Message
, который выводит то же самое сообщение только красным цветом. В строке
client1.UnregisterHandler(New Client.AccountStateHandler(AddressOf Color_Message))
мы удаляем этот метод из списка вызовов делегата, поэтому
этот метод больше не будет срабатывать. Консольный вывод будет иметь следующую форму:
Сумма 150 снята со счета Сумма 150 снята со счета Недостаточно денег на счете Недостаточно денег на счете Сумма 50 снята со счета