Наряду со свойствами и методами классы и интерфейсы могут иметь делегаты и события. Делегаты представляют такие объекты, которые указывают на другие методы. При этом делегаты и методы, на которые ссылаются делегаты, должны иметь те же параметры и тот же тип возвращаемого значения. Создадим два делегата:
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 снята со счета