Делегаты

Последнее обновление: 30.10.2015

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

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 снята со счета
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850