что такое delegate в c
Делегаты (Руководство по программированию на C#)
Делегат — это тип, который представляет ссылки на методы с определенным списком параметров и типом возвращаемого значения. При создании экземпляра делегата этот экземпляр можно связать с любым методом с совместимой сигнатурой и типом возвращаемого значения. Метод можно вызвать (активировать) с помощью экземпляра делегата.
Делегаты используются для передачи методов в качестве аргументов к другим методам. Обработчики событий — это ничто иное, как методы, вызываемые с помощью делегатов. При создании пользовательского метода класс (например, элемент управления Windows) может вызывать этот метод при появлении определенного события. В следующем примере показано объявление делегата:
Делегату можно назначить любой метод из любого доступного класса или структуры, соответствующей типу делегата. Этот метод должен быть статическим методом или методом экземпляра. Такая гибкость позволяет программно изменять вызовы метода, а также включать новый код в существующие классы.
В контексте перегрузки метода его сигнатура не содержит возвращаемое значение. Однако в контексте делегатов сигнатура метода содержит возвращаемое значение. Другими словами, метод должен иметь тот же возвращаемый тип, что и делегат.
Благодаря возможности ссылаться на метод как на параметр делегаты идеально подходят для определения методов обратного вызова. Можно написать метод, сравнивающий два объекта в приложении. Этот метод можно использовать в делегате для алгоритма сортировки. Поскольку код сравнения отделен от библиотеки, метод сортировки может быть более общим.
В C# 9 для похожих сценариев, где требуется больший контроль над соглашением о вызовах, были добавлены указатели на функции. Код, связанный с делегатом, вызывается с помощью виртуального метода, добавленного к типу делегата. Используя указатели функций, можно указать другие соглашения.
Общие сведения о делегатах
Делегаты имеют следующие свойства.
В этом разделе
Спецификация языка C#
Дополнительные сведения см. в разделе Делегаты в Спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
Делегаты, события и лямбды
Делегаты
Определение делегатов
Делегат Message в качестве возвращаемого типа имеет тип void (то есть ничего не возвращает) и не принимает никаких параметров. Это значит, что этот делегат может указывать на любой метод, который не принимает никаких параметров и ничего не возвращает.
Рассмотрим примение этого делегата:
Здесь сначала мы определяем делегат:
В данном случае делегат определяется внутри класса, но также можно определить делегат вне класса внутри пространства имен.
Для использования делегата объявляется переменная этого делегата:
С помощью свойства DateTime.Now.Hour получаем текущий час. И в зависимости от времени в делегат передается адрес определенного метода. Обратите внимание, что методы эти имеют то же возвращаемое значение и тот же набор параметров (в данном случае отсутствие параметров), что и делегат.
Затем через делегат вызываем метод, на который ссылается данный делегат:
Вызов делегата производится подобно вызову метода.
Посмотрим на примере другого делегата:
В данном случае делегат Operation возвращает значение типа int и имеет два параметра типа int. Поэтому этому делегату соответствует любой метод, который возвращает значение типа int и принимает два параметра типа int. В данном случае это методы Add и Multiply. То есть мы можем присвоить переменной делегата любой из этих методов и вызывать.
Делегаты необязательно могут указывать только на методы, которые определены в том же классе, где определена переменная делегата. Это могут быть также методы из других классов и структур.
Присвоение ссылки на метод
Оба способа равноценны.
Соответствие методов делегату
Этому делегату соответствует, например, следующий метод:
А следующие методы НЕ соответствуют:
Здесь метод SomeMethod2 имеет другой возвращаемый тип, отличный от типа делегата. SomeMethod3 имеет другой набор параметров. Параметры SomeMethod4 и SomeMethod5 также отличаются от параметров делегата, поскольку имеют модификаторы ref и out.
Добавление методов в делегат
При добавлении делегатов следует учитывать, что мы можем добавить ссылку на один и тот же метод несколько раз, и в списке вызова делегата тогда будет несколько ссылок на один и то же метод. Соответственно при вызове делегата добавленный метод будет вызываться столько раз, сколько он был добавлен:
При удалении методов из делегата фактически будет создаватья новый делегат, который в списке вызова методов будет содержать на один метод меньше.
Объединение делегатов
Делегаты можно объединять в другие делегаты. Например:
В данном случае объект mes3 представляет объединение делегатов mes1 и mes2. Объединение делегатов значит, что в список вызова делегата mes3 попадут все методы из делегатов mes1 и mes2. И при вызове делегата mes3 все эти методы одновременно будут вызваны.
Вызов делегата
В примерах выше делегат вызывался как обычный метод. Если делегат принимал параметры, то при ее вызове для параметров передавались необходимые значения:
Другой способ вызова делегата представляет метод Invoke() :
Если делегат принимает параметры, то в метод Invoke передаются значения для этих параметров.
Следует учитывать, что если делегат пуст, то есть в его списке вызова нет ссылок ни на один из методов (то есть делегат равен Null), то при вызове такого делегата мы получим исключение, как, например, в следующем случае:
Поэтому при вызове делегата всегда лучше проверять, не равен ли он null. Либо можно использовать метод Invoke и оператор условного null:
Если делегат возвращает некоторое значение, то возвращается значение последнего метода из списка вызова (если в списке вызова несколько методов). Например:
Делегаты как параметры методов
Также делегаты могут быть параметрами методов:
Делегаты (C++/CX)
delegate ключевое слово используется для объявления ссылочного типа, который является среда выполнения Windows эквивалентом объекта функции в стандартном C++. Объявление делегата похоже на сигнатуру функции; оно определяет тип возвращаемого значения и типы параметров, которые должна иметь заключенная в оболочку функция. Ниже показано пользователем объявление делегата:
Делегаты наиболее часто используются в сочетании с событиями. Событие имеет тип делегата; эта связь во многом похожа на отношения между классом и типом интерфейса. Делегат представляет контракт, которому должны соответствовать обработчики событий. Ниже показан член класса события, типом которого является ранее объявленный делегат:
при объявлении делегатов, которые будут предоставляться клиентам через двоичный интерфейс приложения среда выполнения Windows, используйте Windows:: Foundation:: TypedEventHandler. Этот делегат имеет предопределенные двоичные прокси и заглушки, которые позволяют его использовать клиентами Javascript.
Использование делегатов
При создании обработчика событий Visual Studio выполняет многие действия автоматически. Например, при определении обработчика событий в разметке XAML появляется подсказка. Если щелкнуть подсказку, Visual Studio автоматически создаст метод обработчика событий и свяжет его с событием в классе публикации.
В следующем примере демонстрируется использование основного подхода. Windows::Foundation::TypedEventHandler — это тип делегата. Функция обработчика создана с помощью именованной функции.
Как правило, для обработчика событий лучше использовать именованную функцию, а не лямбда-выражение, чтобы не пришлось внимательно следить за возникновением циклических ссылок. Именованная функция перехватывает указатель this с помощью слабой ссылки, в то время как лямбда-выражение перехватывает его с помощью строгой ссылки и создает циклическую ссылку. Дополнительные сведения см. в разделе слабые ссылки и критические циклы.
по соглашению имена делегатов обработчиков событий, определяемые среда выполнения Windows, имеют форму * EventHandler, например RoutedEventHandler, SizeChangedEventHandler или SuspendingEventHandler. Также общепринято, что делегаты обработчиков событий имеют два параметра и возвращают значение void. В делегате, у которого нет параметров-типов, первый параметр принадлежит к типу Platform::Object^; он хранит ссылку на объект-отправитель, инициировавший событие. Необходимо выполнить приведение обратно в исходный тип, прежде чем использовать аргумент в методе обработчика событий. В делегате обработчика событий, имеющем параметры-типы, первый параметр-тип указывает тип отправителя, а второй параметр является дескриптором класса ссылки, который содержит сведения о событии. По соглашению этот класс называется * EventArgs. Например, делегат RoutedEventHandler имеет второй параметр типа RoutedEventArgs^, и DragEventHander имеет второй параметр типа DragEventArgs^.
В соответствии с общепринятой практикой делегаты, являющиеся оболочками для кода, который выполняется при завершении асинхронной операции, имеют имя *CompletedHandler. Эти делегаты определяются как свойства класса, а не как события. Поэтому оператор += не используется, чтобы подписаться на них; вместо этого объект делегата просто присваивается свойству.
C++ IntelliSense не показывает полную сигнатуру делегата, а потому не помогает определить конкретный тип параметра EventArgs. Чтобы определить его тип, можно открыть Обозреватель объектов и просмотреть сведения о методе Invoke делегата.
Создание пользовательских делегатов
можно определить собственные делегаты, определить обработчики событий или позволить потребителям передавать пользовательские функции компоненту среда выполнения Windows. как и любой другой тип среда выполнения Windows, открытый делегат не может быть объявлен как универсальный.
Объявление
при ссылке на тип делегата используется символ «^», точно так же, как и любой среда выполнения Windows ссылочный тип.
Объявление события всегда имеет тип делегата. в этом примере показана типичная сигнатура типа делегата в среда выполнения Windows:
Сначала клиентский код создает экземпляр делегата, используя ref new и предоставляя лямбда-выражение, совместимое с сигнатурой делегата, и определяет пользовательское поведение.
Затем он вызывает функцию-член и передает делегат. Предположим, что ci является экземпляром класса ContactInfo^ и textBlock является конструкцией TextBlock^ XAML-кода.
в следующем примере клиентское приложение передает пользовательский делегат в открытый метод в среда выполнения Windows компоненте, который выполняет делегат для каждого элемента в Vector :
Строительство
Делегат можно создать из любого из следующих объектов:
В следующем примере показано, как создать делегат из каждого из этих объектов. Принципы использования делегата абсолютно не зависят от типа объекта, из которого создан его экземпляр.
Универсальные делегаты
В следующем примере объявляется специальный экземпляр делегата внутри определения класса:
Делегаты и потоки
Делегат, как и объект функции, содержит код, выполняемый в некий момент в будущем. Если код, который создает и передает делегат, и функция, которая принимает и выполняет этот делегат, выполняются в одном и том же потоке, действия относительно просты. Если этот поток является потоком пользовательского интерфейса, делегат может напрямую манипулировать объектами пользовательского интерфейса, такими как элементы управления XAML.
если клиентское приложение загружает среда выполнения Windows компонент, работающий в потоковой подразделении, и предоставляет делегат для этого компонента, по умолчанию делегат вызывается непосредственно в потоке STA. большинство компонентов среда выполнения Windows могут работать в STA или MTA.
Если код, который выполняет делегат, выполняется в другом потоке (например, в контексте объекта concurrency::task), вам необходимо синхронизировать доступ к общим данным. Например, если делегат содержит ссылку на объект Vector и элемент управления XAML содержит ссылку на тот же самый объект Vector, необходимо принять меры, чтобы избежать взаимоблокировки и состояния гонки, которые могут возникнуть, если делегат и элемент управления XAML попытаются обратиться к объекту Vector одновременно. Необходимо также обеспечить, чтобы делегат не пытался захватить с помощью ссылок локальные переменные, которые могут оказаться вне области до того, как произойдет вызов делегата.
Делегаты и события в C#
Перевод статьи подготовлен специально для студентов курса «Разработчик С#».
Что такое события в C#?
Понимание делегатов в C#
В C# делегаты образуют основные строительные блоки для событий. Делегат — это тип, который определяет сигнатуру метода. Например, в C++ это можно сделать с помощью указателя на функцию. В C# вы можете создать экземпляр делегата, указывающий на другой метод. Вы можете вызвать этот метод через экземпляр делегата.
Ниже приведен пример объявления делегата и вызова метода через него.
Использование делегата в C#
Как видите, мы используем ключевое слово delegate, чтобы сообщить компилятору, что мы создаем тип делегата.
Инстанцировать делегаты легко вместе с автоматическим созданием нового типа делегата.
Для создания делегата вы также можете использовать ключевое слово new.
MathDelegate mathDelegate = new MathDelegate(Add) ;
Инстанцированный делегат является объектом; Вы можете также использовать его и передавать в качестве аргумента другим методам.
Многоадресные делегаты в C#
Например, чтобы узнать, сколько методов будет вызывать многоадресный делегат, вы можете использовать следующий код:
Ковариантность и контравариантность в C#
Когда вы назначаете делегату метод, сигнатура метода не обязательно должна точно соответствовать делегату. Это называется ковариацией и контравариантностью. Ковариация позволяет методу иметь более производный тип возвращаемого значения, нежели тот, который определен в делегате. Контравариантность разрешает метод с типами параметров, которые являются менее производными, чем типы в делегате.
Ковариация в делегатах
Вот пример ковариации,
Контравариантность в делегатах
Ниже приведен пример контравариантности.
Вы можете узнать больше об этой концепции здесь.
Лямбда-выражения в C#
Иногда вся сигнатура метода может требовать больше кода, чем само тело метода. Существуют также ситуации, в которых вам нужно создать целый метод только для того, чтобы использовать его в делегате.
Для этих случаев Microsoft добавила некоторые новые возможности в C#, например, анонимные методы в 2.0. В C# 3.0 дела стали обстоять еще лучше, когда были добавлены лямбда-выражения. Лямбда-выражение является предпочтительным способом при написании нового кода.
Ниже приведен пример новейшего лямбда-синтаксиса.
Для чтения этого кода вам нужно использовать слово “следует” в контексте специального лямбда-синтаксиса. Например, первое лямбда-выражение в вышеприведенном примере читается как «x и y следуют к сложению x и y».
Лямбда-функция не имеет конкретного имени в отличии от метода. Из-за этого лямбды называются анонимными функциями. Вам также не нужно явно указывать тип возвращаемого значения. Компилятор предполагает его автоматически из вашей лямбды. И в случае вышеприведенного примера типы параметров x и y также не указаны явно.
Вы можете создавать лямбды, которые охватывают несколько операторов. Вы можете сделать это, добавив фигурные скобки вокруг операторов, которые образуют лямбду, как показано в примере ниже.
Типы Func можно найти в пространстве имен System. Они представляют делегаты, которые возвращают тип и принимают от 0 до 16 параметров. Все эти типы наследуются от System.MulticaseDelegate для того, чтобы вы могли добавить несколько методов в список вызовов.
Если вам нужен тип делегата, который не возвращает значение, вы можете использовать типы System.Action. Они также могут принимать от 0 до 16 параметров, но не возвращают значение.
Вот пример использования типа Action,
Все усложняется, когда ваша лямбда-функция начинает ссылаться на переменные, объявленные вне лямбда-выражения или на this. Обычно, когда элемент управления покидает область действия переменной, переменная становится недействительной. Но что, если делегат ссылается на локальную переменную. Чтобы исправить это, компилятор генерирует код, который продлевает срок службы захваченной переменной, по крайней мере, до тех пор, пока живет самый долгоживущий делегат. Это называется замыканием.
Вы можете узнать больше о замыканиях здесь.
События в C#
Рассмотрим популярный шаблон разработки — издатель-подписчик (pub/sub). Вы можете подписаться на событие, а затем вы будете уведомлены, когда издатель события инициирует новое событие. Эта система используется для установления слабой связи между компонентами в приложении.
Делегат формирует основу для системы событий в C#.
Затем метод в другом классе может подписаться на событие, добавив один из его методов в делегат события:
Ниже приведен пример, показывающий, как класс может предоставить открытый делегат и генерировать событие.
Он не позволяет использовать = (прямое назначение делегата). Следовательно, ваш код теперь защищен от риска удаления предыдущих подписчиков, используя = вместо +=.
Кроме того, вы могли заметить специальный синтаксис инициализации поля OnChange для пустого делегата, такого как delegate < >. Это гарантирует, что наше поле OnChange никогда не будет null. Следовательно, мы можем удалить null-проверку перед тем, как вызвать событие, если нет других членов класса, делающих его null.
Когда вы запускаете вышеуказанную программу, ваш код создает новый экземпляр Pub, подписывается на событие двумя разными методами и генерирует событие, вызывая p.Raise. Класс Pub совершенно не осведомлен ни об одном из подписчиков. Он просто генерирует событие.
Вы также можете прочитать мою статью «Шаблон проектирования издатель-подписчик в C#» для более глубокого понимания этой концепции.
Что ж, на этом пока это все. Надеюсь, вы уловили идею. Спасибо за прочтение поста. Пожалуйста, дайте мне знать, если есть какие-либо ошибки или необходимы изменения в комментарии ниже. Заранее спасибо!
System.Delegate и ключевое слово delegate
Определение типов делегатов
В качестве примера будем по-прежнему использовать метод List.Sort(). Первым шагом является создание типа для делегата сравнения.
Обратите внимание, что синтаксис может отображаться так, будто он объявляет переменную, но на самом деле он объявляет тип. Можно определить типы делегатов внутри классов, непосредственно внутри пространств имен или даже в глобальном пространстве имен.
Не рекомендуется объявлять типы делегатов (или другие типы) непосредственно в глобальном пространстве имен.
Компилятор также создает обработчики добавления и удаления для этого нового типа, чтобы клиенты этого класса могли добавлять и удалять методы из списка вызовов экземпляра. Компилятор будет обеспечивать соответствие подписи добавляемого или удаляемого метода подписи, используемой при объявлении метода.
Объявление экземпляров делегатов
После определения делегата можно создать экземпляр этого типа. Как и все переменные в C#, экземпляры делегата нельзя объявлять непосредственно в пространстве имен или в глобальном пространстве имен.
В приведенном выше фрагменте кода была объявлена переменная-член в классе. Можно также объявить переменные делегатов, локальные переменные или аргументов для методов.
Вызов делегатов
Чтобы вызвать методы, которые находятся в списке вызова делегата, нужно вызвать этот делегат. В методе Sort() код вызовет метод сравнения, чтобы определить порядок размещения объектов:
В строке выше код вызывает метод, подключенный к делегату. Переменная считается именем метода и вызывается с помощью обычного синтаксиса вызова метода.
Назначение, добавление и удаление целевых объектов вызова
Сведения о том, как определяется тип делегата и как объявляются и вызываются экземпляры делегата.
Предположим, требуется отсортировать список строк по их длине. Функция сравнения может выглядеть следующим образом:
Метод объявляется как закрытый метод. Все правильно. Вам может быть не нужно, чтобы этот метод был частью общедоступного интерфейса. Этот метод, присоединенный к делегату, по-прежнему можно использовать в качестве метода сравнения. В вызывающем коде этот метод будет присоединен к целевому списку объекта делегата и будет доступен через этот делегат.
Чтобы создать эту связь, передайте метод в метод List.Sort() :
Обратите внимание, что имя метода используется без скобок. Использование метода как аргумента указывает компилятору преобразовать ссылку на метод в ссылку, которая может применяться как целевой объект вызова делегата, и присоединить этот метод в качестве целевого объекта вызова.
Вы также явно объявили переменную типа Comparison и выполнили назначение:
Если в качестве объекта делегата используется небольшой метод, для назначения обычно применяется синтаксис лямбда-выражения:
Использование лямбда-выражений для целевых объектов делегатов рассматривается более подробно в следующем разделе.
В примере Sort() к делегату обычно подключается один целевой метод. Однако объекты делегатов поддерживают списки вызовов, где к объекту делегата присоединено несколько целевых методов.
Классы Delegate и MulticastDelegate
Класс System.Delegate и его прямой вложенный класс System.MulticastDelegate обеспечивают поддержку платформы для создания делегатов, регистрации методов в качестве целевых объектов делегатов и вызова всех методов, которые зарегистрированы как целевые объекты делегатов.
Наилучший способ обеспечения безопасности типа заключался в том, что компилятор должен был создать конкретные классы делегатов, представляющих используемую сигнатуру метода.
Несмотря на то, что невозможно создать производные классы напрямую, вы будете использовать методы, определенные в этих классах. Разберем наиболее распространенные методы, которые будут использоваться при работе с делегатами.
Ознакомившись с синтаксисом языка и классами, поддерживающими делегаты, давайте рассмотрим способы использования, создания и вызова строго типизированных делегатов.