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

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

    Начиная с этого шага мы рассмотрим несколько примеров, которые иллюстрируют красоту и мощь делегатов. Начнем с примера, в котором экземпляр делегата (точнее, переменная, которая может ссылаться на экземпляр делегата) является полем в классе.

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

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

    // Класс с полем, являющимся ссылкой на экземпляр делегата: 
    class MyClass {
        // Поле является ссылкой на экземпляр делегата:
        public MyDelegate apply;
        // Конструктор:
        public MyClass(MyDelegate md) { 
            apply = md;
        }
    }

    // Класс: 
    class Alpha {
        // Закрытое текстовое поле: 
        private string name;
        // Метод для присваивания значения полю: 
        public void set(string t) { 
            name = t;
        }
        // Переопределение метода ToString(): 
        public override string ToString() { 
            return name;
        }
    }

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

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


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

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

  delegate void MyDelegate(string txt);   . 

    Экземпляры делегата смогут ссылаться на методы, имеющие один текстовый аргумент и не возвращающие результат. В классе MyClass описано поле apply, типом которого указан делегат MyDelegate. Это означает, что поле apply может ссылаться на метод (или список методов). Но самое важное - мы можем "вызывать" это поле (передав ему один текстовый аргумент). Также в классе описан конструктор с одним аргументом. Аргумент конструктора тоже примечательный - это переменная md типа MyDelegate. В теле конструктора командой

  apply = md;
аргумент, переданный конструктору, присваивается значением полю apply.


Если в качестве типа переменной указан делегат, то такая переменная, по своей сути, является ссылкой на метод (который соответствует параметрам делегата). Во всяком случае, значением переменной может присваиваться ссылка на метод. Правда происходит все немного сложнее: когда переменной типа делегата присваивается ссылка на метод, создается экземпляр делегата, переменная ссылается на этот экземпляр делегата, а экземпляр делегата ссылается на метод. Фактически экземпляр делегата является посредником между переменной и методом. Но в практическом плане, для понимания происходящего, обычно можно отождествлять переменную типа делегата (как поле apply или аргумент конструктора класса MyClass) со ссылкой на метод.

    Еще один класс Alpha имеет закрытое текстовое поле name. Для присваивания значения полю предусмотрен открытый метод set(), имеющий текстовый аргумент и не возвращающий результат. Для считывания значения поля name мы переопределяем метод ToString() (метод результатом возвращает значение поля name).

    В методе Main() командой

  Alpha A = new Alpha();
создается объект класса Alpha. После этого командой
  MyClass obj = new MyClass(A.set);
мы создаем объект класса MyClass. Пикантность ситуации в том, что аргументом конструктору класса MyClass передается ссылка A.set на метод set() объекта A.


В несколько упрощенном виде общая последовательность действий такая. Аргумент конструктора класса MyClass объявлен как переменная типа MyDelegate. При вызове конструктора для записи аргумента выделяется место в памяти - то есть создается техническая переменная типа MyDelegate. Аргументом конструктору передается ссылка A.set, которая и присваивается технической переменной. В результате создается экземпляр делегата, который ссылается на метод set() объекта A, а техническая переменная (аргумент конструктора) ссылается на этот экземпляр. В теле конструктора, при выполнении команды
  apply = md;    , 
значение технической переменной копируется в поле apply. В результате поле apply ссылается на экземпляр делегата, который ссылается на метод set() объекта A.

    Поэтому при выполнении команды

  obj.apply("Объект A");    , 
вызывающей экземпляр делегата, на который ссылается поле apply, с аргументом "Объект A" вызывается метод set() объекта A. Полю name объекта A присваивается значение, что подтверждается при выполнении команды
  Console.WriteLine(A);   . 

    Затем командой

  Alpha B = new Alpha();
создается еще один объект класса Alpha. С помощью команды
  obj.apply = B.set;
поле apply связывается с методом set() объекта B. Поэтому после выполнения команды
  obj.apply("Объект B");
присваивается значение полю name объекта B. Это значение проверяем командой
  Console.WriteLine(B);   .

    Командой

  obj.apply += A.set;
в список вызовов экземпляра делегата apply добавляется метод set() объекта A. И когда выполняется команда
  obj.apply("Объект X");       , 
то из объектов B и A последовательно вызывается метод set() с аргументом "Объект X". В результате поле name каждого из объектов получает значение "Объект X", в чем мы и убеждаемся с помощью команды
  Console.WriteLine(A + " и " + B);    .

    Наконец, выполнение команды

  obj.apply -= B.set;
приводит к удалению метода set() объекта B из списка вызовов экземпляра делегата apply. Поэтому при выполнении команды
  obj.apply("Объект A");
меняется значение поля name только для объекта A.

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




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