Перфолента.NET. ООП. Конструируем класс. Делегаты
Конструируем класс. Делегаты.
Делегаты играют очень важную роль в современном программировании. А какую именно роль они играют и как их использовать на практике, Вы узнаете в этой статье.
Делегаты в обычной жизни - это люди, которые представляют интересы других людей. Делегат в программировании – это объект, представляющий один или несколько методов (процедур или функций). Можно условно сказать, что делегат хранит ссылки на методы.
Для чего это может быть нужно?
Чаще всего, делегаты используются тогда, когда на этапе проектирования программы не известно, какой метод необходимо будет вызвать. Например, разработчик библиотеки определяет событие, которое может произойти во время работы, а другой разработчик, который использует библиотеку, передает ей делегат на метод, который должен выполниться, когда событие произойдет. Или ещё пример, Вы можете создать универсальную процедуру рисующую график любой математической функции, а затем передавать ей делегаты методов, вычисляющих функции, которые необходимо нарисовать.
До появления делегатов, во многих языках использовались указатели на методы, представлявшие собой адрес в оперативной памяти, где начинается код метода. Можно было заставить процессор перепрыгнуть на этот адрес и начать выполнение кода метода. Однако, управлять памятью и вычислять где начинается необходимый Вам метод, довольно сложно. К тому же, адрес в памяти ничего не говорит о том, что это за метод, сколько и какие у него параметры. Вы легко можете что-то перепутать и вызвать не тот метод или передать не верный набор параметров. Более того, Вы вообще можете использовать не верный адрес и передать управление не известно куда. Безопасность на нуле. А самое плохое, что компилятор никак не может указать Вам на возможную ошибку.
Делегаты лишены указанных недостатков. Во-первых, делегат имеет тип совместимый с сигнатурой того метода, который Вы можете вызвать, т.е. порядок и типы параметров метода строго определены. Во-вторых, Вы не можете указать какой попало адрес в памяти, зато можете легко указать не только общий для класса метод, но и метод конкретного экземпляра объекта! Не спрашивайте меня как это работает, но платформа .Net с этим легко и эффективно справляется.
Рассмотрим использование делегатов в языке программирования Перфолента.Net.
Под капотом.
Платформа .Net содержит два базовых класса для работы с делегатами: System.Delegate и System.MulticastDelegate. Первый представляет собой базовый класс для делегатов, содержащих одну ссылку на метод, а второй является потомком первого и может содержать несколько ссылок на методы. Эти классы не предназначены для непосредственного использования. Они предназначены для использования компиляторами. Однако, в некоторых библиотеках, тип параметра или возвращаемое значение, могут быть одного из этих типов.
В языке программирования Перфолента.Net типу System.Delegate соответствует встроенный тип БазовыйДелегат, а типу System.MulticastDelegate соответствует встроенный тип БазовыйМультиДелегат.
Компилятор Перфоленты все создаваемые делегаты наследует от класса БазовыйМультиДелегат.
Определение класса делегата.
С точки зрения языка Перфолента.Net, делегат – это специализированный класс. Поэтому определить собственный делегат можно на уровне классов (см. статью Структура программы на языке Перфолента.Net).
В отличие от определения класса, определение делегата не является блочным. Однако, как и класс, делегат может иметь атрибуты.
Синтаксис определения делегата похож на синтаксис определения метода с добавлением перед ним ключевого слова Делегат:
[&Атрибут1, … АтрибутН] Делегат Процедура|Функция|Метод ИмяДелегата( [Параметр1 тип ТипПараметра1, … ПараметрН тип ТипПараметраН] ) [ тип ТипВозвращаемогоЗначения]
Здесь в квадратных скобках указаны части определения, которые могут отсутствовать, а вертикальная черта разделяет альтернативные части заменяющие друг друга.
Как видно из этого определения, атрибуты могут отсутствовать, делегат может быть определен для процедуры или функции (альтернативное ключевое слово Метод может представлять, как процедуру, так и функцию), параметры могут отсутствовать, а тип возвращаемого значения указывается только для функций.
Приведем примеры определения классов делегатов:
//делегат, который сможет указывать на процедуру не имеющую параметров Делегат Процедура ПроцедураБезПараметров() //делегат с атрибутом, видимый только в пределах текущей сборки &ВидноСборке Делегат Процедура СекретнаяПроцедура(Идентификатор тип Строка) //делегат, который сможет указывать на функцию с заданными типами //параметра и возвращаемого значения Делегат Функция КвадратЧисла(Значение тип Число) тип Число
Примеры с ключевым словом Метод:
//метод без возвращаемого значения является процедурой Делегат Метод ОбработчикМоегоСобытия(Источник тип Объект) //метод с возвращаемым значением является функцией Делегат Метод ИсточникСигналов(Время тип Дата) тип ДВещ
Умнику на заметку: Важно! Сигнатура метода, используемая при определении делегата, включает в себя тип возвращаемого значения. А сигнатура метода, используемая при перегрузке методов, НЕ включает в себя тип возвращаемого значения!
Создание экземпляра объекта делегата.
В текущей версии языка Перфолента.Net, основным способом создания экземпляра объекта делегата, содержащего ссылку на указанный метод, является встроенная функция ПолучитьДелегат.
Умнику на заметку: Функция ПолучитьДелегат является функцией времени компиляции, а модуль Делегаты из стандартной библиотеки, позволяет получать делегаты на лету во время выполнения программы. В будущих версиях компилятора можно будет получить делегат на анонимные методы и лямбда-выражения.
Синтаксис функции ПолучитьДелегат выглядит так:
ПолучитьДелегат([Объект], ИмяМетодаОбъекта, ["ТипДелегата"])
В квадратных скобках указаны параметры, которые в определенных случаях можно не указывать.
Рассмотрим параметры:
Объект – это выражение, возвращающее экземпляр объекта, которому принадлежит метод. Если метод находится в модуле, то вместо объекта необходимо указать имя модуля. Если метод является общим для класса, то вместо объекта необходимо указать имя класса. Этот параметр можно не указывать, если метод, для которого необходимо получить делегат, находится в том же модуле (или классе для общих для класса методов), откуда вызывается функция ПолучитьДелегат.
ИмяМетодаОбъекта – это имя метода, для которого необходимо получить делегат.
"ТипДелегата" – это тип делегата, имеющего сигнатуру совместимую с методом, для которого необходимо получить делегат. Тип делегата можно не указывать, если компилятор может его определить из контекста вызова функции ПолучитьДелегат. Например, если делегат передаётся как фактический параметр функции, то тип формального параметра функции и является типом получаемого делегата.
Приведём примеры:
Пример 1: Метод, для которого необходимо получить делегат, находится в том же модуле, откуда вызывается функция ПолучитьДелегат.
Программа
//есть определение типа делегата Делегат Функция КвадратЦелогоЧисла(Х тип Целое) тип Целое
//есть функция возвращающая квадрат целого числа //и по сигнатуре соответствующая делегату Функция Квадрат(Х тип Целое) тип Целое Возврат Х*Х КонецФункции
//в этой процедуре нам надо получить и использовать делегат Процедура Старт() //получим делегат необходимой функции и сохраним в переменную. //тип делегата указываем в третьем параметре ДКвадрат = ПолучитьДелегат( , Квадрат, "КвадратЦелогоЧисла") //вызываем функцию через делегат и выводим результат в консоль ВыводСтроки ДКвадрат.Выполнить(5) //остановимся, что бы увидеть результат в консоли ВводСтроки КонецПроцедуры
КонецПрограммы
В этом примере функция Квадрат и процедура Старт находятся в одном модуле Программа. Поэтому нет необходимости указывать первый параметр функции ПолучитьДелегат.
Обратите внимание, что у делегата есть метод Выполнить, с помощью которого можно вызвать методы, ссылки на которые содержит экземпляр делегата.
Пример 2: Метод, для которого необходимо получить делегат, находится в другом модуле.
Программа
//в этой процедуре нам надо получить и использовать делегат Процедура Старт() //получим делегат необходимой функции и сохраним в переменную //полный тип делегата указываем в третьем параметре ДКвадрат = ПолучитьДелегат(Общее, Квадрат, "Общее.КвадратЦелогоЧисла") //вызываем функцию через делегат и выводим результат в консоль ВыводСтроки ДКвадрат.Выполнить(5) //остановимся, что бы увидеть результат в консоли ВводСтроки КонецПроцедуры
КонецПрограммы
Модуль Общее
//есть определение типа делегата &ВидноВсем Делегат Функция КвадратЦелогоЧисла(Х тип Целое) тип Целое
//есть функция возвращающая квадрат целого числа //и по сигнатуре соответствующая делегату &ВидноВсем Функция Квадрат(Х тип Целое) тип Целое Возврат Х*Х КонецФункции
КонецМодуля
В этом примере функция Квадрат и определение делегата находятся в модуле Общее, а процедура Старт находится в модуле Программа. Поэтому в функции ПолучитьДелегат в качестве первого параметра необходимо указать модуль Общее, а в третьем параметре указать полный тип делегата "Общее.КвадратЦелогоЧисла", включающий имя модуля, где расположено определение делегата.
Пример 3: Получаем делегаты на методы конкретных экземпляров объектов.
//есть определение типа делегата Делегат Метод МетодМяу()
Программа
//в этой процедуре нам надо получить и использовать делегаты Процедура Старт() //создадим двух котов с разным голосом КотВася = Новый Кот("Мя-уууууу!") КотЖуля = Новый Кот("Мя-аааа-у!") //получим делегаты на метод Голос для каждого кота ДГолосВаси = ПолучитьДелегат(КотВася, Голос, "МетодМяу") ДГолосЖули = ПолучитьДелегат(КотЖуля, Голос, "МетодМяу") //вызываем голоса котов через делегаты ДГолосВаси.Выполнить() ДГолосЖули.Выполнить() //остановимся, что бы увидеть результат в консоли ВводСтроки КонецПроцедуры
КонецПрограммы
Класс Кот
//в этом поле храним голос кота Поле _Голос тип Строка //конструктор позволяет задать голос новому коту &ВидноВсем Конструктор(пГолос тип Строка) _Голос = пГолос КонецКонструктора //выводит голос кота в консоль &ВидноВсем Метод Голос() ВыводСтроки _Голос КонецМетода
КонецКласса
В этом примере в первый параметр функции ПолучитьДелегат передаются разные экземпляры объектов класса Кот. Поэтому метод делегата Выполнить вызывает метод Голос соответствующего экземпляра объекта класса кота.
Операции с делегатами.
Выполнение методов делегата.
Смысл делегата в том, чтобы в нужный момент можно было бы выполнить методы, ссылки на которые содержит делегат. Для этого в языке программирования Перфолента.Net у делегата предусмотрены методы Выполнить и ВыполнитьДинамически.
Рассмотрим примеры.
Методу Выполнить необходимо передать фактические параметры, соответствующие типам параметров в сигнатуре делегата.
В примере 3, делегат МетодМяу не имеет параметров, поэтому и метод Выполнить вызывается без параметров:
ДГолосВаси.Выполнить()
А в следующем примере, делегат имеет два параметра, поэтому и в метод Выполнить необходимо передать два параметра соответствующего типа:
Делегат Процедура МетодОтправкиСигнала(Код тип Целое, Текст тип Строка)
Программа
Процедура Старт() //получим делегат Сигнализатор = ПолучитьДелегат( , ОтправитьСигнал, "МетодОтправкиСигнала") //выполним метод Сигнализатор.Выполнить(404, "Объект не найден") //остановимся, что бы увидеть результат в консоли ВводСтроки КонецПроцедуры
Процедура ОтправитьСигнал(Код тип Целое, Текст тип Строка) ВыводСтроки "Код " + Код + " Текст " + Текст КонецПроцедуры
КонецПрограммы
Метод ВыполнитьДинамически принимает массив параметров типа Объект.
Добавим в предыдущий пример следующие строки:
Параметры = {404, "Объект не найден"} Сигнализатор.ВыполнитьДинамически(Параметры)
Сначала мы с помощью инициализатора создали массив Параметры типа Объект[], а затем передали его методу ВыполнитьДинамически.
Метод Выполнить работает значительно быстрее, чем метод ВыполнитьДинамически, т.к. его параметры связываются с вызываемым методом на стадии компиляции.
Сложение и вычитание делегатов.
Делегат может содержать ссылки на один или несколько методов. Поэтому язык программирования Перфолента.Net поддерживает сложение и вычитание делегатов.
Важно! Сложение и вычитание делегатов поддерживаются только для делегатов одного типа!
Добавим в пример 3 несколько строк демонстрирующих сложение и вычитание делегатов:
//делегат может содержать несколько ссылок на методы //поэтому сложим делегаты в один и вызовем сразу два метода ДГолосВаси += ДГолосЖули ВыводСтроки "После суммы:" ДГолосВаси.Выполнить() //вызывает два метода сразу
//просто сложим делегаты и вызовем сразу три метода, //ведь в ДГолосВаси уже лежат 2 метода... СуммаДелегатов = ДГолосВаси + ДГолосЖули ВыводСтроки "После суммы:" СуммаДелегатов.Выполнить() //вызывает три метода сразу
//вычтем один делегат СуммаДелегатов -= ДГолосЖули ВыводСтроки "После вычитания:" СуммаДелегатов.Выполнить() //вызывает два метода
Проверка делегатов на равенство.
Два делегата можно сравнить между собой.
Что бы считаться равными, два делегата должны:
· иметь одинаковый тип;
· иметь идентичный список вызываемых методов, включая сигнатуры методов, принадлежность методов к экземплярам объектов, а также порядок методов в списке вызова делегата.
Добавим в пример 3 несколько строк демонстрирующих проверку на равенство делегатов:
Если ДГолосВаси = ДГолосЖули
ВыводСтроки "Делегаты равны!"
ИначеЕсли ДГолосВаси <> ДГолосЖули
ВыводСтроки "Делегаты НЕ равны!"
КонецЕсли
В данном случае делегаты окажутся не равными, т.к. относятся к разным экземплярам объектов.
Получение и обработка списка вызываемых методов делегата.
Делегат может содержать сразу несколько ссылок на методы и иногда может понадобиться поработать с каждым из них отдельно. Это можно сделать с помощью метода ВсеДелегаты, который возвращает массив делегатов типа БазовыйДелегат, содержащихся в списке вызова исходного делегата.
Для Дел тип БазовыйДелегат Из СуммаДелегатов.ВсеДелегаты() Цикл
Дел.ВыполнитьДинамически() ТипКТипу(Дел, "МетодМяу").Выполнить()
КонецЦикла;
Свойства делегата Метод и Объект.
Как мы уже знаем, делегат содержит ссылку либо на общий для класса метод, либо на метод конкретного экземпляра объекта. Можно проверить свойство Объект делегата, и если значение равно Неопределено, то ссылка указывает на общий для класса метод, иначе, если значение свойства заполнено, то оно содержит экземпляр того объекта, чей метод будет вызываться с помощью делегата.
КотВася = ДГолосВаси.Объект
Свойство Метод делегата возвращает информацию о вызываемом методе.
МетодМяуИнфо = ДГолосВаси.Метод
Работа с делегатами в стандартной библиотеке.
Стандартная библиотека языка программирования Перфолента.Net содержит дополнительные возможности для работы с делегатами.
Преобразование типа делегата.
В глобальном модуле стандартной библиотеки есть функция КопироватьДелегатКак, которая позволяет сделать копию делегата, при этом изменив его тип.
Допустим, у нас есть два определения делегатов: Делегат Метод МетодМяу() Делегат Метод МетодГав()
Получим делегат типа МетодГав и преобразуем его в делегат типа МетодМяу: ДГолосВасиГав = ПолучитьДелегат(КотВася, Голос, "МетодГав") ВыводСтроки ТипЗнчСтр(ДГолосВасиГав) //выводит МетодГав ДГолосВасиМяу = КопироватьДелегатКак<МетодМяу>(ДГолосВасиГав) ВыводСтроки ТипЗнчСтр(ДГолосВасиМяу) //выводит МетодМяу
Обратите внимание, что методу КопироватьДелегатКак необходимо указать в угловых скобках новый тип делегата, в данном случае МетодМяу, а затем передать в качестве фактического параметра старый делегат, в данном случае ДГолосВасиГав.
Такие преобразования делегатов нужны редко, но тем не менее иногда нужны.
Модуль Делегаты.
Модуль Делегаты стандартной библиотеки позволяет на лету получить делегат не только на метод класса, но и на конструктор, поле, свойство, событие. Более того, вы можете получить делегаты для не видимых (приватных) членов класса, к которым обычным способом доступ получить нельзя.
Как можно получить делегат на поле, если выше везде было написано, что делегат может содержать исключительно ссылки на методы? Не волнуйтесь, на самом деле библиотека строит на лету метод для получения значения поля, а потом создает и возвращает Вам делегат на этот метод.
В модуле Делегаты довольно много методов, поэтому для выбора необходимого метода, лучше всего воспользоваться справочной информацией из синтакс-помощника.
Делегаты общего назначения.
Стандартная библиотека содержит множество типов делегатов общего назначения, которые можно использовать в своих программах.
Самые полезные из них:
Делегат Процедура ОбработчикСобытияБезПараметров(Источник тип Объект) Делегат Процедура ОбработчикСобытия(Источник тип Объект, Параметры тип ПараметрыСобытия) Делегат Процедура ОбработчикСобытияСОтменой(Источник тип Объект, Параметры тип ПараметрыСобытияСОтменой) Делегат Процедура ОбработчикПередДействием(Источник тип Объект, Ссыл Отказ тип Булево, Ссыл СтандартнаяОбработка тип Булево) Делегат Процедура ОбработчикПереопределяемогоДействия(Источник тип Объект, Ссыл СтандартнаяОбработка тип Булево) Делегат Процедура ОбработчикСтрокиДанных(Источник тип Объект, СтрокаДанных тип Строка) Делегат Процедура ОбработчикВыбораОбъекта(Источник тип Объект, ВыбранныйОбъект тип Объект, Ссыл СтандартнаяОбработка тип Булево)
Если Вам подходит по смыслу один из этих делегатов, то используйте его, вместо создания нового типа делегата.
Универсальные делегаты.
Есть ещё группа универсальных делегатов, которые используются в методах стандартной библиотеки и могут применяться в Ваших программах. Универсальные делегаты делятся на две группы, в первой параметры имеют тип Объект, а во второй используются обобщенные параметры.
Универсальные делегаты с параметрами типа Объект.
Это делегаты процедур с именами от ПроцедураСПараметрами1 до ПроцедураСПараметрами8, где число в имени процедуры соответствует количеству параметров типа Объект. То же самое с делегатами функций от ФункцияСПараметрами1 до ФункцияСПараметрами8, у которых от 1 до 8 параметров типа Объект и возвращаемое значение типа Объект. Есть и делегаты ПроцедураБезПараметров и ФункцияБезПараметров.
Универсальные делегаты с обобщенными параметрами.
Это делегаты процедур с именами от ДействиеПроц1 до ДействиеПроц8 и делегаты функций с именами от ДействиеФунк1 до ДействиеФунк8, где число в имени процедуры или функции соответствует количеству обобщенных параметров. Аналогично, существуют делегаты без параметров ДействиеПроц и ДействиеФунк.
Больше информации по делегатам, определенным в стандартной библиотеке Перфоленты, можно получить из справки в синтакс-помощнике.
Вывод: Делегаты обеспечивают возможность получать ссылки на методы, хранить их и передавать в другие методы в качестве фактических параметров. На делегатах в платформе .Net базируются система событий, анонимные методы, лямбды, расширения перечисляемых коллекций и многие другие возможности библиотек, методам которых требуется вызывать другие методы.
Сергей Рогаткин
23.09.2024 01:49