Делегаты | Delegates

Делегаты | Delegates

Делегат — класс, который хранит указатель на метод. В классе-делегате строго указаны все его входные и выходные (ref и out) параметры, а также возвращаемое значение(сигнатура).

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

Возможность делать вызовы методов с заранее определёнными сигнатурами — это и есть самая суть делегатов. Экземпляр делегата хранит ссылку/указатель на целевой метод и, если этот метод является экземплярным (non-static класс), то и ссылку на экземпляр объекта (класса или структуры), в котором «находится» целевой метод.

Тип делегата объявляется при помощи ключевого слова delegate. Типы делегатов могут существовать как самостоятельные сущности (объявлены в пространстве имен), так и быть объявленными внутри классов или структур.

По отношению модификаторов доступа типы делегатов ведут себя так же, как классы и структуры. Если для типа делегата явно не указан модификатор доступа и этот тип объявлен внутри пространства имён, то он будет по умолчанию internal (будет доступен для всех объектов, также находящихся внутри этого пространства имён). Если же тип делегата без модификатора объявлен внутри класса или структуры, то по умолчанию он будет private. При объявлении типа делегата нельзя использовать модификатор static.

Ключевое слово delegate не всегда означает объявление типа делегата. Это же ключевое слово используется при создании экземпляров делегатов при использовании анонимных методов (Подробнее: Анонимная функция | Anonymous function).

Объявление открытого делегата, который ничего не принимает, и не возвращает:

public delegate void MyDelegate();

Делегаты наследуются от абстрактного класса System.MulticastDelegate, который, в свою очередь, наследуется от абстрактного классаSystem.Delegate.

На практике стоит учитывать наследование только от MulticastDelegate. Различие между Delegate и MulticastDelegate лежит прежде всего в историческом аспекте. Эти различия были существенны в бета-версиях .NET 1.0, но это было неудобно, и Microsoft решила объединить два типа в один. К сожалению, решение было сделано слишком поздно, и когда оно было сделано, делать такое серьёзное изменение, затрагивающее основу .NET, не решились. Поэтому можно считать, что Delegate и MulticastDelegate — это одно и то же.

Рассмотрим пару примеров.

Объявим класс-делегат:

public delegate void MyDelegate();

В случае, если класс static:

MyDelegate myDelegate = new MyDelegate(Example.Method);

В случае non-static класса, необходимо сначала создать его экземпляр, после чего инициализировать делегат его методом.

Example instance = new Example();
MyDelegate myDelegate = new MyDelegate(instance.Method);

Также необязательно вызывать конструктор класса-делегата (предположение делегата):

MyDelegate myDelegate = Example.Method; или MyDelegate myDelegate = instance.Method;

Также можно заnullить делегат при инициализации, а потом добавлять в него методы.

Action d1 = null;
d1 += Method_1;
d1 += Method_2;

Каждый тип делегата, наследует члены от MulticastDelegate, а именно: один конструктор с параметрами Object и IntPtr, а также три метода: Invoke, BeginInvoke и EndInvoke. Эти три метода не наследуются в прямом смысле, так как их сигнатура для каждого типа делегата своя — она «подстраивается» под сигнатуру метода в объявленном типе делегата.


Комбинированный делегат

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

Фактически, комбинированные делегаты являются списками простых делегатов, все из которых основываются на одном типе делегата (т.е. имеют одинаковую сигнатуру методов, на которые ссылаются).

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

Список простых делегатов в комбинированном делегате называется «списком вызовов» или «списком действий» (invocation list). Список вызовов — это список пар ссылок (на метод и экземпляр объекта), которые расположены в порядке вызова.

Важно знать, что экземпляры делегатов всегда неизменяемы (immutable). Каждый раз при объединении\вычитании экземпляров делегатов создаётся новый комбинированный делегат.

Объединение (также встречается термин «сложение») двух экземпляров делегатов обычно производится при помощи оператора сложения +, как если бы экземпляры делегатов были числами или строками. Аналогично, вычитание (также встречается термин «удаление») одного экземпляра делегата из другого производится при помощи оператора вычитания -.

Важно помнить, что при вычитании одного комбинированного делегата из другого, вычитание производится в рамках списка вызовов. Если в оригинальном (уменьшаемом) списке вызовов нет не одного из тех простых делегатов, которые находятся в вычитаемом списке вызовов, то результатом операции (разностью) будет оригинальный список. В противном случае, если в оригинальном списке присутствуют простые делегаты, присутствующие и в вычитаемом, то в результирующем списке будут отсутствовать лишь последние вхождения простых делегатов.

В таблице ниже, литералами d1, d2, d3 обозначены простые делегаты. Обозначение [d1, d2, d3] подразумевает комбинированный делегат, который состоит из трёх простых именно в таком порядке, т.е. при вызове сначала будет вызван d1, потом d2, а затем d3. Пустой список вызовов представлен значением null.

Выражение Результат
null + d1 d1
d1 + null d1
d1 + d2 [d1, d2]
d1 + [d2, d3] [d1, d2, d3]
[d1, d2] + [d2, d3] [d1, d2, d2, d3]
[d1, d2] — d1 d2
[d1, d2] — d2 d1
[d1, d2, d1] — d1 [d1, d2]
[d1, d2, d3] — [d1, d2] d3
[d1, d2, d3] — [d2, d1] [d1, d2, d3]
[d1, d2, d3, d1, d2] — [d1, d2] [d1, d2, d3]
[d1, d2] — [d1, d2] null

Важно! При вычитании комбинированных делегатов важна последовательность простых делегатов в списках вызовов вычитаемых.

Кроме оператора сложения, экземпляры делегатов могут объединяться при помощи статического метода Delegate.Combine ,аналогично ему, операция вычитания имеет альтернативу в виде статического метода Delegate.Remove.

Вообще, операторы сложения и вычитания — это своеобразный синтаксический сахар, и компилятор C#, встречая их в коде, заменяет на вызовы методов Combine и Remove. И именно потому, что данные методы являются статическими, они легко справляются с null-экземплярами делегатов.

Добавление и удаление делегатов происходит с конца списка, поэтому последовательность вызововx += y; x -= y;эквивалентна пустой операции.

Важно! Если сигнатура типа делегата объявлена такой, что возвращает значение (т.е. возвращаемое значение не является void) и «на основе» этого типа создан комбинированный экземпляр делегата, то при его вызове в переменную будет записано возвращаемое значение, «предоставленное» последним простым делегатом в списке вызовов комбинированного делегата.

Важно! Если есть комбинированный делегат (содержащий список вызовов, состоящий из множества простых делегатов), и при его вызове в каком-то простом делегате произойдёт исключение, то в этом месте вызов комбинированного делегата прекратится, исключение будет проброшено, и все остальные простые делегаты из списка вызовов так никогда и не будут вызваны.

results for ""

    No results matching ""