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