Шаг 168.
Язык программирования C#. Начала.
Делегаты и события. Многократная адресация

    На этом шаге мы рассмотрим особенности использования такой адресации.

    Делегаты поддерживают многократную адресацию. Это означает, что экземпляр делегата может ссылаться не на один, а сразу на несколько методов. Если так, то при вызове делегата выполняется цепочка вызовов: последовательно вызываются методы, на которые ссылается вызываемый экземпляр делегата. Методы вызываются в той последовательности, в которой ссылки на методы добавлялись экземпляру делегата. Для добавления ссылки на метод экземпляру делегата используется оператор += (команда вида экземпляр+=метод) или полная версия операции присваивания вида экземпляр=экземпляр+метод.

    Таким образом, мы можем связать с экземпляром делегата не только отдельный метод, но и целый список методов. Если впоследствии нам понадобится удалить ссылку на метод из списка методов, на которые ссылается экземпляр делегата, можно воспользоваться оператором -= (команда вида экземпляр-=метод) или командой вида экземпляр=экземпляр-метод. В примере ниже представлена программа, в которой используется многократная адресация для экземпляра делегата.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr168_1
{
    // Объявление делегата: 
    delegate void MyDelegate();

    // Класс: 
    class MyClass {
        // Текстовое поле: 
        public string name;
        // Конструктор с текстовым аргументом: 
        public MyClass(string txt) { 
            name = txt;
        }
        // Метод без аргументов: 
        public void show() {
            Console.WriteLine(name);
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Статический метод без аргументов: 
        static void makeLine() {
            Console.WriteLine("--------");
        }
        // Главный метод: 
        static void Main()
        {
            // Создание объектов:
            MyClass A = new MyClass("Объект A");
            MyClass B = new MyClass("Объект B");
            MyClass C = new MyClass("Объект C");
            // Объявление переменной делегата:
            MyDelegate meth;
            // Присваивание переменной делегата ссылки на метод: 
            meth = A.show;
            // Вызов экземпляра делегата: 
            meth();
            // Присваивание переменной делегата нового значения: 
            meth = makeLine;
            // Добавление методов в список вызова:
            meth += A.show;
            meth += B.show;
            meth = meth + C.show;
            // Вызов экземпляра делегата: 
            meth();
            // Удаление метода из списка вызова: 
            meth -= B.show;
            // Вызов экземпляра делегата: 
            meth();
            // Удаление метода из списка вызова: 
            meth = meth - A.show;
            // Вызов экземпляра делегата: 
            meth();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

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


Рис.1. Результат выполнения программы

    Делегат MyDelegate объявлен командой

   delegate void MyDelegate();   . 

    Экземпляры делегата могут ссылаться на методы, не имеющие аргументов и не возвращающие результат. В программе описывается класс MyClass с открытым текстовым полем name. У класса есть конструктор с текстовым аргументом, который присваивается в качестве значения полю name создаваемого объекта. Также в классе описан метод show(), при вызове отображающий в консольном окне значение текстового поля name. У метода нет аргументов, и он не возвращает результат.

    В классе Program, кроме главного метода Main(), есть еще и статический метод makeLine(). У метода makeLine() нет аргументов, и он не возвращает результат. При вызове метода в консольном окне отображается импровизированная "линия" из дефисов.


Таким образом, и статический метод makeLine(), и нестатический метод show() из класса MyClass соответствуют "требованиям", которые "задекларированы" при объявлении делегата MyDelegate. Поэтому экземпляр делегата может ссылаться на оба эти метода.

    В методе Main() последовательно создаются три объекта A, B и C класса MyClass. Поле name каждого из объектов получает уникальное текстовое значение. Командой

  MyDelegate meth;
объявляется переменная meth делегата MyDelegate. Пока эта переменная не ссылается на экземпляр делегата - мы его еще не создали. Экземпляр делегата создается (и ссылка на него записывается в переменную meth) при выполнении команды
  meth = A.show;      , 
которой ссылка на метод show() объекта A присваивается переменной meth. Команда выполняется так: создается экземпляр делегата, который ссылается на метод show() объекта A, и ссылка на этот экземпляр записывается в переменную meth.


Уместно напомнить, что доступ к экземпляру делегата мы получаем через переменную делегата (аналог объектной переменной). Если такой переменной в качестве значения присвоить ссылку на метод, то автоматически будет создан экземпляр делегата, ссылающегося на метод, а в переменную будет записана ссылка на экземпляр делегата. Командой MyDelegate meth мы только объявили переменную. Экземпляр делегата создается, когда переменной meth присваивается значение (ссылка на метод). В принципе, можно создавать экземпляр делегата и с помощью выражения на основе new-инструкции.

    При выполнении команды meth() из объекта A вызывается метод show(). Затем выполняется команда

  meth = makeLine;     . 
Формально здесь переменной meth присваивается новое значение. Но в действительности происходит следующее. Создается новый экземпляр делегата, который ссылается на статический метод makeLine(). Ссылка на экземпляр делегата записывается в переменную meth, а ее связь с прежним экземпляром делегата (который ссылался на метод show() объекта A) теряется. Внешне все выглядит так, как если бы экземпляр делегата, отождествляемый с переменной meth, получал ссылку на метод makeLine(). А вот выполнение команды
  meth += A.show;
приводит к тому, что в экземпляр делегата добавляется еще и ссылка на метод show() объекта A. При этом метод makeLine() также остается в списке вызовов экземпляра делегата. Командами
  meth += B.show;
и
  meth = meth + C.show;
в список вызовов последовательно добавляются методы show() объектов B и C. Поэтому при вызове экземпляра делегата командой meth() будут выполнены вызовы makeLine(), A.show(), B.show() и C.show() - строго в той последовательности, в которой методы добавлялись в список вызовов.

    После выполнения команды

  meth -= B.show;
из списка вызовов экземпляра делегата meth удаляется метод show() объекта B. При выполнении вызова экземпляра делегата командой meth() теперь последовательно выполняются вызовы makeLine(), A.show() и C.show(). Если после этого выполнить команду
  meth = meth - A.show;   , 
то из списка вызовов будет удален еще и метод show() объекта A. Вызов экземпляра делегата командой meth() приведет к выполнению вызовов makeLine() и C.show().


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

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


    На следующем шаге мы рассмотрим использование делегатов.




Предыдущий шаг Содержание Следующий шаг