На этом шаге мы особенности создания и использования делегатов.
Ранее мы рассматривали различные классы, на основе которых создавались объекты. Мы явно или неявно исходили из того, что класс представляет собой некоторый аналог "типа данных", с поправкой на то обстоятельство, что в классе "спрятаны" не только данные (или средства для их реализации), но и некоторая "функциональность" в виде кода для обработки этих данных. Теперь нам предстоит познакомиться с еще более специфическим "типом" - речь идет о делегатах. Чтобы понять, что же такое делегат, удобно представлять делегат как некий аналог класса. Так же как на основе класса создается объект, на основе делегата тоже создается объект. Мы будем называть такой объект экземпляром делегата. Экземпляр делегата - это такой специфический объект, который может ссылаться на методы.
Правда мы еще не знаем, что такое ссылка на метод. Но в этом случае можно отталкиваться от позитивистской концепции: есть некий объект (экземпляр делегата), который связан с некоторым методом: объект "знает", что это за метод и где его "искать". Объект можно вызывать, как метод. При вызове объекта на самом деле вызывается метод, с которым связан объект.
Как же описывается делегат и как на его основе создавать экземпляры (и, самое главное, что с ними потом делать)? Начнем с описания делегата. Чтобы понять логику того, как описывается делегат, следует еще раз вспомнить, зачем он нужен. Нужен делегат для создания объектов, которые будут ссылаться на методы. А что важно при работе с методом? Какие параметры или характеристики могут использоваться для "классификации" методов? Несложно сообразить, что это тип результата и количество и тип аргументов. Поэтому когда мы описываем делегат, то должны явно указать, на какие методы смогут ссылаться экземпляры делегата. Другими словами, нам необходимо указать, какого типа результат и какого типа аргументы должны быть у метода, чтобы экземпляр данного делегата мог ссылаться на метод.
Именно эти характеристики указываются при описании делегата. Начинается оно с ключевого слова delegate. Далее указывается идентификатор типа - это идентификатор типа результата метода, на который сможет ссылаться экземпляр делегата. Например, если указано ключевое слово int, то это означает, что экземпляр делегата сможет ссылаться на методы, которые результатом возвращают int-значение. Далее, после идентификатора типа указывается название делегата. Это имя, которое мы даем делегату, сродни имени класса. После имени делегата в круглых скобках описываются аргументы (указывается тип и формальное название аргумента). Это аргументы, которые должны быть у метода для того, чтобы ссылку на метод можно было присвоить экземпляру делегата. Общий шаблон объявления делегата такой:
delegate тип имя(аргументы);
Фактически, если взять сигнатуру (тип результата, имя и список аргументов) метода, на который может ссылаться экземпляр делегата, заменить в сигнатуре название метода на название делегата и указать в самом начале ключевое слово delegate, мы получим объявление делегата. Например, мы хотим описать делегат с названием MyDelegate, экземпляру которого можно было бы присвоить ссылку на метод. У метода два аргумента (типа int и string) и результат типа char. Такой делегат объявлялся бы следующей инструкцией:
delegate char MyDelegate(int k, string txt);
Это объявление делегата. Но делегат, напомним, нужен для того, чтобы создать объект (экземпляр делегата). Схема здесь достаточно простая, и внешне все напоминает создание объекта класса, где роль класса играет делегат. Мы можем использовать следующий шаблон:
делегат переменная = new делегат(метод);
В данном случае инструкцией вида new делегат (метод) создается экземпляр делегата, а ссылка на этот экземпляр записывается в переменную. Экземпляр делегата, на который ссылается переменная, сам ссылается на метод, указанный в инструкции new делегат (метод).
Возвращаясь к примеру с делегатом MyDelegate, мы могли бы создать экземпляр этого делегата командой такого вида:
MyDelegate meth = new MyDelegate(метод);
Данной командой объявляется объектная переменная meth типа MyDelegate (фактически объектная переменная), в эту переменную записывается ссылка на созданный объект (экземпляр делегата MyDelegate), а сам этот объект ссылается на метод, указанный в круглых скобках в инструкции new MyDelegate(метод). Осталось осветить вопрос с тем, как выполняется ссылка на метод. Здесь все очень просто: для выполнения ссылки на метод достаточно указать имя этого метода (без круглых скобок и аргументов). Если речь идет о выполнении ссылки на нестатический метод, то указывается имя объекта и название метода (разделенные точкой), то есть ссылка на нестатический метод выполняется в формате объект.метод. Если метод статический, то ссылка на него выполняется в формате класс.метод, то есть указывается имя класса и после него, через точку - имя метода. Например, если нужно выполнить ссылку на метод method(), который должен вызываться из объекта obj, то она будет выглядеть как obj.method. Команда создания экземпляра делегата, ссылающегося на метод method() объекта obj, выглядит так:
MyDelegate meth = new MyDelegate(obj.method);
Если метод method() статический в классе MyClass, то ссылка на такой метод выполняется как MyClass.method. Команда создания экземпляра делегата, ссылающегося на статический метод method() класса MyClass, выглядит следующим образом:
MyDelegate meth = new MyDelegate(MyClass.obj);
После того как мы создали экземпляр делегата, с его помощью можно вызывать метод, на который экземпляр ссылается. Для этого достаточно "вызвать" экземпляр делегата. Например, если экземпляр делегата meth ссылается на метод method() объекта obj, и мы хотим вызвать этот метод с аргументами number (целое число типа int) и text (значение типа string), то мы можем воспользоваться командой meth(number, text), которая будет означать выполнение инструкции obj.method(number, text). Если экземпляр делегата meth ссылается на статический метод method() класса MyClass, то для выполнения инструкции MyClass.method(number, text) можно воспользоваться командой meth(number, text).
Количество и тип аргументов, которые следует передать экземпляру делегата при вызове, определяются тем, как описан соответствующий делегат. Скажем, если делегат MyDelegate описан так, как указано выше, то экземпляр делегата MyDelegate следует вызывать с двумя аргументами: целочисленным и текстовым, - в соответствии с тем, какие аргументы указаны после названия делегата MyDelegate в его объявлении.
Описанный выше способ создания экземпляра делегата - не единственный и не самый простой. Можно пойти более радикальным путем: объявить переменную, тип которой является делегатом, и присвоить такой переменной ссылку на метод. Шаблон команды следующий:
делегат переменная = метод;
Вернемся к делегату MyDelegate и методу method(): для нестатического метода все могло бы выглядеть так:
MyDelegate meth = obj.method;
Если метод статический, то экземпляр делегата можно создать таким образом:
MyDelegate meth = MyClasss.method;
При присваивании ссылки на метод переменной типа делегата происходит следующее: создается экземпляр делегата, который ссылается на данный метод, а ссылка на созданный экземпляр делегата присваивается переменной.
На следующем шаге мы рассмотрим небольшой пример использования делегатов.