Как связать делегат с функцией

Обновлено: 01.05.2024

В этой статье показано, как определить и использовать делегаты в C++/CLI.

В следующем примере кода определяется делегат с именем MyCallback . Код обработки событий — это функция, которая вызывается при срабатывании нового делегата, должна иметь тип возвращаемого значения void и принимать String ссылку.

Функция Main использует статический метод, определяемый SomeClass для создания экземпляра MyCallback делегата. Затем делегат станет альтернативным методом вызова этой функции, как показано путем отправки строки "Single" в объект делегата. Затем дополнительные экземпляры MyCallback связываются вместе, а затем выполняются одним вызовом к объекту делегата.

В следующем примере кода показано, как связать делегат с членом класса значений.

Создание делегатов

Оператор " - " можно использовать для удаления делегата компонента из составного делегата.

Выходные данные

Передать делегат ^ в собственную функцию, которая принимает указатель на функцию

Из управляемого компонента можно вызвать собственную функцию с параметрами указателя функции, где собственная функция затем может вызывать функцию-член делегата управляемого компонента.

Этот образец создает .dll, который экспортирует собственную функцию:

В следующем примере используется .dll и передача обработчика делегата в собственную функцию, ожидающую указатель на функцию.

Выходные данные

Связывание делегатов с неуправляемыми функциями

Чтобы связать делегат с собственной функцией, необходимо заключить собственную функцию в управляемый тип и объявить функцию, которая будет вызываться с помощью PInvoke .

Выходные данные

Использование несвязанных делегатов

Несвязанный делегат можно использовать для передачи экземпляра типа, функция которого должна вызываться при вызове делегата.

Несвязанные делегаты особенно полезны, если требуется выполнить итерацию по объектам в коллекции — с помощью for each, в ключевом слове — и вызвать функцию-член для каждого экземпляра.

Вот как объявить, создать экземпляр и вызвать привязку и несвязанные делегаты:

Чтобы указать функцию экземпляра, первый параметр является экземпляром типа, функция-член которого требуется вызвать, а второй параметр — адрес функции, которую необходимо вызвать.

В этом образце показано, как объявить, создать экземпляр и вызвать несвязанные делегаты:

Выходные данные

В следующем примере показано, как использовать несвязанные делегаты и ключевые слова For Each для перебора объектов в коллекции и вызова функции-члена для каждого экземпляра.

Этот пример создает непривязанный делегат для функций доступа к свойству:

Выходные данные

В следующем примере показано, как вызвать делегат многоадресной рассылки, если один экземпляр привязан и один экземпляр не привязан.

Выходные данные

В следующем примере показано, как создать и вызвать непривязанный универсальный делегат.

Action

Делегат Action представляет некоторое действие, которое ничего не возвращает, то есть в качестве возвращаемого типа имеет тип void :

Данный делегат имеет ряд перегруженных версий. Каждая версия принимает разное число параметров: от Action до Action . Таким образом можно передать до 16 значений в метод.

Как правило, этот делегат передается в качестве параметра метода и предусматривает вызов определенных действий в ответ на произошедшие действия. Например:

Predicate

Делегат Predicate<T> принимает один параметр и возвращает значение типа bool :

Как правило, используется для сравнения, сопоставления некоторого объекта T определенному условию. В качестве выходного результата возвращается значение true, если условие соблюдено, и false, если не соблюдено:

В данном случае возвращается true или false в зависимости от того, больше нуля число или нет.

Еще одним распространенным делегатом является Func . Он возвращает результат действия и может принимать параметры. Он также имеет различные формы: от Func() , где T - тип возвращаемого значения, до Func() , то есть может принимать до 16 параметров.

Данный делегат также часто используется в качестве параметра в методах:

Метод DoOperation() в качестве параметра принимает делегат Func , то есть ссылку на метод, который принимает число int и возвращает также значение int.

При первом вызове метода DoOperation() ему передается ссылка на метод DoubleNumber, который увеличивает число в два раза. Во втором случае передается метод SquareNumber - опять же метод, который принимает параметр типа int и возвращает результат в виде значения int.

Здесь переменная createString представляет лямбда-выражение, которое принимает два числа int и возвращает строку, то есть представляет делегат Func .

Делегаты представляют такие объекты, которые указывают на методы. То есть делегаты - это указатели на методы и с помощью делегатов мы можем вызвать данные методы.

Определение делегатов

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

Делегат Message в качестве возвращаемого типа имеет тип void (то есть ничего не возвращает) и не принимает никаких параметров. Это значит, что этот делегат может указывать на любой метод, который не принимает никаких параметров и ничего не возвращает.

Рассмотрим примение этого делегата:

Прежде всего сначала необходимо определить сам делегат:

Для использования делегата объявляется переменная этого делегата:

Далее в делегат передается адрес определенного метода (в нашем случае метода Hello). Обратите внимание, что данный метод имеет тот же возвращаемый тип и тот же набор параметров (в данном случае отсутствие параметров), что и делегат.

Затем через делегат вызываем метод, на который ссылается данный делегат:

Вызов делегата производится подобно вызову метода.

При этом делегаты необязательно могут указывать только на методы, которые определены в том же классе, где определена переменная делегата. Это могут быть также методы из других классов и структур.

Место определения делегата

Либо вне класса:

Параметры и результат делегата

Рассмотрим определение и применение делегата, который принимает параметры и возвращает результат:

В данном случае делегат Operation возвращает значение типа int и имеет два параметра типа int. Поэтому этому делегату соответствует любой метод, который возвращает значение типа int и принимает два параметра типа int. В данном случае это методы Add и Multiply. То есть мы можем присвоить переменной делегата любой из этих методов и вызывать.

Поскольку делегат принимает два параметра типа int, то при его вызове необходимо передать значения для этих параметров: operation(4,5) .

Присвоение ссылки на метод

Выше переменной делегата напрямую присваивался метод. Есть еще один способ - создание объекта делегата с помощью конструктора, в который передается нужный метод:

Оба способа равноценны.

Соответствие методов делегату

Как было написано выше, методы соответствуют делегату, если они имеют один и тот же возвращаемый тип и один и тот же набор параметров. Но надо учитывать, что во внимание также принимаются модификаторы ref , in и out . Например, пусть у нас есть делегат:

Этому делегату соответствует, например, следующий метод:

А следующие методы НЕ соответствуют:

Здесь метод SomeMethod2 имеет другой возвращаемый тип, отличный от типа делегата. SomeMethod3 имеет другой набор параметров. Параметры SomeMethod4 и SomeMethod5 также отличаются от параметров делегата, поскольку имеют модификаторы ref и out.

Добавление методов в делегат

В примерах выше переменная делегата указывала на один метод. В реальности же делегат может указывать на множество методов, которые имеют ту же сигнатуру и возвращаемые тип. Все методы в делегате попадают в специальный список - список вызова или invocation list. И при вызове делегата все методы из этого списка последовательно вызываются. И мы можем добавлять в этот список не один, а несколько методов. Для добавления методов в делегат применяется операция += :

В данном случае в список вызова делегата message добавляются два метода - Hello и HowAreYou. И при вызове message вызываются сразу оба этих метода.

Однако стоит отметить, что в реальности будет происходить создание нового объекта делегата, который получит методы старой копии делегата и новый метод, и новый созданный объект делегата будет присвоен переменной message.

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

Подобным образом мы можем удалять методы из делегата с помощью операций -= :

При удалении методов из делегата фактически будет создаватья новый делегат, который в списке вызова методов будет содержать на один метод меньше.

Стоит отметить, что при удалении метода может сложиться ситуация, что в делегате не будет методов, и тогда переменная будет иметь значение null. Поэтому в данном случае переменная определена не просто как переменная типа Message , а именно Message? , то есть типа, который может представлять как делегат Message, так и значение null.

Кроме того, перед вторым вызовом мы проверяем переменную на значение null.

При удалении следует учитывать, что если делегат содержит несколько ссылок на один и тот же метод, то операция -= начинает поиск с конца списка вызова делегата и удаляет только первое найденное вхождение. Если подобного метода в списке вызова делегата нет, то операция -= не имеет никакого эффекта.

Объединение делегатов

Делегаты можно объединять в другие делегаты. Например:

В данном случае объект mes3 представляет объединение делегатов mes1 и mes2. Объединение делегатов значит, что в список вызова делегата mes3 попадут все методы из делегатов mes1 и mes2. И при вызове делегата mes3 все эти методы одновременно будут вызваны.

Вызов делегата

В примерах выше делегат вызывался как обычный метод. Если делегат принимал параметры, то при его вызове для параметров передавались необходимые значения:

Другой способ вызова делегата представляет метод Invoke() :

Если делегат принимает параметры, то в метод Invoke передаются значения для этих параметров.

Следует учитывать, что если делегат пуст, то есть в его списке вызова нет ссылок ни на один из методов (то есть делегат равен Null), то при вызове такого делегата мы получим исключение, как, например, в следующем случае:

Поэтому при вызове делегата всегда лучше проверять, не равен ли он null. Либо можно использовать метод Invoke и оператор условного null:

Если делегат возвращает некоторое значение, то возвращается значение последнего метода из списка вызова (если в списке вызова несколько методов). Например:

Обобщенные делегаты

Делегаты, как и другие типы, могут быть обобщенными, например:

Здесь делегат Operation типизируется двумя параметрами типов. Параметр T представляет тип возвращаемого значения. А параметр K представляет тип передаваемого в делегат параметра. Таким образом, этому делегату соответствует метод, который принимает параметр любого типа и возвращает значение любого типа.

В прогамме мы можем определить переменные делегата под определенный метод. Например, делегату Operation соответствует метод, который принимает число int и возвращает число типа decimal. А делегату Operation соответствует метод, который принимает и возвращает число типа int.

Делегаты как параметры методов

Также делегаты могут быть параметрами методов. Благодаря этому один метод в качестве параметров может получать действия - другие методы. Например:

Здесь метод DoOperation в качестве параметров принимает два числа и некоторое действие в виде делегата Operation. В внутри метода вызываем делегат Operation, передавая ему числа из первых двух параметров.

При вызове метода DoOperation мы можем передать в него в качестве третьего параметра метод, который соответствует делегату Operation.

Возвращение делегатов из метода

Также делегаты можно возвращать из методов. То есть мы можем возвращать из метода какое-то действие в виде другого метода. Например:

В данном случае метод SelectOperation() в качестве параметра принимает перечисление типа OperationType. Это перечисление хранит три константы, каждая из которых соответствует определенной арифметической операции. И в самом методе в зависимости от значения параметра возвращаем определенный метод. Причем поскольку возвращаемый тип метода - делегат Operation, то метод должен возвратить метод, который соответствует этому делегату - в нашем случае это методы Add, Subtract, Multiply. То есть если параметр метода SelectOperation равен OperationType.Add , то возвращается метод Add, который выолняет сложение двух чисел:

При вызове метода SelectOperation мы можем получить из него нужное действие в переменную operation:

И при вызове переменной operation фактически будет вызываться полученный из SelectOperation метод:

В прошлой теме подробно были рассмотрены делегаты. Однако данные примеры, возможно, не показывают истинной силы делегатов, так как нужные нам методы в данном случае мы можем вызвать и напрямую без всяких делегатов. Однако наиболее сильная сторона делегатов состоит в том, что они позволяют делегировать выполнение некоторому коду извне. И на момент написания программы мы можем не знать, что за код будет выполняться. Мы просто вызываем делегат. А какой метод будет непосредственно выполняться при вызове делегата, будет решаться потом.

Рассмотрим подробный пример. Пусть у нас есть класс, описывающий счет в банке:

В переменной sum хранится сумма на счете. С помощью конструктора устанавливается начальная сумма на счете. Метод Add() служит для добавления на счет, а метод Take - для снятия денег со счета.

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

Но что если наш класс планируется использовать в других проектах, например, в графическом приложении на Windows Forms или WPF, в мобильном приложении, в веб-приложении. Там строка уведомления

не будет иметь большого смысла.

Более того, наш класс Account будет использоваться другими разработчиками в виде отдельной библиотеки классов. И эти разработчики захотят уведомлять о снятии средств каким-то другим образом, о которых мы даже можем не догадываться на момент написания класса. Поэтому примитивое уведомление в виде строки кода

не самое лучшее решение в данном случае. И делегаты позволяют делегировать определение действия из класса во внешний код, который будет использовать этот класс.

Изменим класс, применив делегаты:

Для делегирования действия здесь определен делегат AccountHandler . Этот делегат соответствует любым методам, которые имеют тип void и принимают параметр типа string .

В классе Account определяем переменную taken , которая представляет этот делегат:

Теперь надо связать эту переменную с конкретным действием, которое будет выполняться. Мы можем использовать разные способы для передачи делегата в класс. В данном случае определяется специальный метод RegisterHandler, который передается в переменную taken реальное действие:

Таким образом, делегат установлен, и теперь его можно вызывать. Вызов делегата производится в методе Take:

Теперь протестируем класс в основной программе:

Здесь через метод RegisterHandler переменной taken в классе Account передается ссылка на метод PrintSimpleMessage . Этот метод соответствует делегату AccountHandler. Соответственно там, где вызывается делегат taken в методе Account, в реальности будет выполняться метод PrintSimpleMessage.

Добавление и удаление методов в делегате

Хотя в примере наш делегат принимал адрес на один метод, в действительности он может указывать сразу на несколько методов. Кроме того, при необходимости мы можем удалить ссылки на адреса определенных методов, чтобы они не вызывались при вызове делегата. Итак, изменим в классе Account метод RegisterHandler и добавим новый метод UnregisterHandler, который будет удалять методы из списка методов делегата:

В первом методе объединяет делегаты taken и del в один, который потом присваивается переменной taken . Во втором методе из переменной taken удаляется делегат del .

Применим новые методы:

В строке account.UnregisterHandler(PrintColorMessage); этот метод удаляется из списка вызовов делегата, поэтому этот метод больше не будет срабатывать. Консольный вывод будет иметь следующую форму:

Делегат — это тип, который безопасно инкапсулирует метод, схожий с указателем функции в C и C++. В отличие от указателей функций в C делегаты объектно-ориентированы, типобезопасны и безопасны. Тип делегата задается его именем. В следующем примере объявляется делегат с именем Del , который может инкапсулировать метод, использующий в качестве аргумента значение string и возвращающий значение void:

Объект делегата обычно создается путем предоставления имени метода, для которого делегат будет служить оболочкой, или с помощью лямбда-выражения. После создания экземпляра делегата вызов метода, выполненный в делегате передается делегатом в этот метод. Параметры, передаваемые делегату вызывающим объектом, передаются в метод, а возвращаемое методом значение (при его наличии) возвращается делегатом в вызывающий объект. Эта процедура называется вызовом делегата. Делегат, для которого создан экземпляр, можно вызвать, как если бы это был метод, для которого создается оболочка. Пример:

Обратный вызов также часто используется для задания настраиваемого метода сравнения и передачи этого делегата в метод сортировки. Это позволяет сделать коду вызывающего объекта частью алгоритма сортировки. В следующем примере метод использует тип Del тип в качестве параметра.

Затем в данный метод можно передать созданный ранее делегат:

и получить следующие выходные данные в окне консоли:

При использовании делегата в качестве абстракции методу MethodWithCallback не нужно выполнять непосредственный вызов консоли, то есть его можно создавать без учета консоли. Метод MethodWithCallback просто подготавливает строку и передает ее в другой метод. Это очень удобно, так как делегируемый метод может использовать любое количество параметров.

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

Вместе с рассмотренным ранее статическим методом DelegateMethod есть три метода, для которых можно создать оболочку с помощью экземпляра Del .

На данном этапе список вызова делегата allMethodsDelegate содержит три метода — Method1 , Method2 и DelegateMethod . Три исходных делегата d1 , d2 и d3 остаются без изменений. При вызове allMethodsDelegate все три метода вызываются по порядку. Если делегат использует параметры, передаваемые по ссылке, эта ссылка передается после каждого из трех методов, а все изменения одного из методов становятся видны в следующем методе. Если любой из методов вызывает неперехваченное исключение, это исключение передается в вызывающий делегат объект, а последующие методы в списке вызова не вызываются. Если делегат имеет возвращаемое значение и (или) выходные параметры, он возвращает возвращаемое значение и параметры последнего вызванного метода. Чтобы удалить метод из списка вызовов, используйте вычитание или операторы присваивания вычитания ( - или -= ). Пример:

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

Делегаты, в списке вызова которых находятся несколько методов, является производным от MulticastDelegate, являющегося подклассом класса System.Delegate . Приведенный выше код работает в любом из случаев, так как оба класса поддерживают GetInvocationList .

Назначение сравнения делегатов двух различных типов во время компиляции вызовет ошибку компиляции. Если экземпляры делегата статически относятся к типу System.Delegate , сравнение допустимо, но во время выполнения будет возвращено значение false. Пример:

Читайте также: