Делегаты Делегатом называется тип, который позволяет хранить ссылки на функции. Несмотря на то что звучит это несколько интригующе, механизм, лежащий в основе этой возможности, удивительно прост. Наиглавнейшее предназначение делегатов вряд ли удастся понять до тех пор, пока вы не познакомитесь с событиями и с их обработкой, однако рассмотрение самого понятия делегатов принесет огромную пользу. Когда позднее наступит время их использовать, они будут вам уже знако- мы, что сделает некоторые сложные темы гораздо более легкими для понимания. Объявление делегатов во многом напоминает объявление функций; при этом отсутствует само тело функции, но добавляется ключевое слово delegate. Объяв- ление делегата определяет сигнатуру функции, состоящую из возвращаемого типа и списка параметров. После объявления делегата мы получаем возможность объя- вить переменную типа этого делегата. Мы можем затем инициализировать эту переменную, присвоив ей ссылку на произвольную функцию, обладающую сигна- турой, совпадающей с сигнатурой делегата. После всего этого мы получаем воз- можность вызывать эту самую функцию с помощью данной переменной-делегата так, как если бы последняя сама являлась этой функцией. Теперь, когда у нас имеется переменная, ссылающаяся на функцию, мы также получаем возможность выполнять и некоторые другие операции, которые не могут быть выполнены с использованием иных средств. Например, появляется возмож- ность передавать переменную-делегат другой функции в качестве параметра, что позволит этой функции использовать данного делегата для вызова функции, на ко- торую он ссылается, без необходимости определять вызываемую функцию до начала выполнения программы. Давайте рассмотрим пример. Функции 129 Практикум: использование делегата для вызова функции 1. Создайте новое консольное приложение с именем chO6ExO5 В директории C:\BegCSharp\Chapter6. 2. Добавьте следующий код в ciassi.cs: class Classl Н Р Т Р Г Г Л Г Р rtnnhi р П Г П Г Р Я Я Л Р ! p n f l t p f r i n n h l р п л г я т 1 . Н п п Ы Р п я г л т Л : static double Multiply(double paraml, double param2) return paraml * param2; static double Divide(double paraml, double param2) return paraml / param2; } static void Main (string [] args) processDelegate process; Console.WriteLine("Enter 2 numbers separated with a comma:*); string input = Console.ReadLine(); int commaPos = input.IndexOf{','); double paraml,= Convert.ToDouble(input.Substring(0, commaPos)) ; double param2 = Convert.ToDouble(input.Substring(commaPos + 1, input.Length - commaPos - 1)) ; Console.WriteLine("Enter M to multiply or D to divide:*); input = Console.ReadLine(); if (input == «M") process = new processDelegate(Multiply); else process = new processDelegate(Divide); Console.WriteLine("Result: {0}", process(paraml, param2)); 3. Запустите программу: EjC\BegCSharp\Chapl:er6\Ch06EK05\bin\l> Enter 2 numbers separated uxth а cororaa 7.2.6.3 Enter Ш to Multiply oi» D to divide" П Result: 45«36 Press any he^ to cosntioue Ш Как это работает Этот код описывает делегата (processDelegate), сигнатура которого совпадает с сигнатурой двух функций: Multiply о и Divide*). Делегат имеет следующее определение: delegate double processDelegate (double paraml, double param2) ; Ключевое слово delegate указывает, что данное определение является опреде- лением делегата, а не функции (это определение располагается в том же месте 130 Глава 6 программы, где могло бы находиться и определение функции). Далее приводится сигнатура, в которой задаются возвращаемое значение типа double и два параметра типа double. Используемые в сигнатуре имена являются произвольными, поэтому тип делегата и имена параметров мы можем выбирать по своему усмотрению. В данном случае мы выбрали имя делегата processDeiegate, а параметры назвали paraml И param2. Код функции Main о начинается с того, что мы объявляем переменную, восполь- зовавшись описанным типом делегата: static void Main (string [] args) { processDeiegate process; Затем следует абсолютно стандартный для С# код, который запрашивает два чис- ла, разделенные запятой, и присваивает их двум переменным типа double: Console.WriteLine('Enter 2 numbers separated with a comma:*); string input = Console.ReadLineO ; int commaPos = input.IndexOf(','); double paraml = Convert.ToDouble(input.Substring(0, commaPos)); double param2 = Convert .ToDouble (input. Substring (commaPos + 1, input.Length - commaPos - 1) ) ; Обратите внимание, что для наглядности в программу не включены никакие проверки допустимости вводимой пользователем информации. В реальной программе нам пришлось бы потратить большое количество времени, проверяя допустимость тех значений, которые мы получаем для ЛОКаЛЬНЫХ Переменных paraml U param2. Затем мы запрашиваем пользователя, следует ли умножать или делить эти числа: Console.WriteLine('Enter M to multiply or D to divide:*); input = Console.ReadLine(); В зависимости от полученного ответа мы инициализируем переменную-делегат: if (input == "М") process = new processDeiegate(Multiply); else process = new processDeiegate(Divide); Для присвоения переменной-делегату ссылки на функцию нам приходится ис- пользовать несколько странно выглядящий синтаксис. Так же, как и в случае при- сваивания значений массиву, для создания нового делегата используется ключевое слово new. После этого слова указывается тип создаваемого делегата и задается параметр, определяющий ту функцию, которую мы хотим использовать, а именно функцию console.writeLineO. Обратите внимание, что этот параметр не совпа- дает ни с параметрами типа делегата, ни с параметрами используемой функции: это уникальный синтаксис, применяемый исключительно для осуществления при- сваивания делегату. В качестве параметра используется просто имя той функции, которую необходимо использовать, без скобок. Наконец, мы вызываем выбранную нами функцию с помощью делегата. Здесь применяется один и тот же синтаксис, независимо от того, на какую именно функ- цию ссылается делегат: Console.WriteLine('Result: {0}*, process(paraml, param2)); Функции 131 В данном случае мы обращаемся с переменной-делегатом так, как если бы она представляла собой имя функции. Однако, в отличие от функций, над этой пере- менной можно выполнять некоторые дополнительные операции, например, переда- чу ее другой функции в качестве параметра. Простой пример такого использования может выглядеть примерно следующим образом: static void executeFunction(processDelegate process) •; { process(2.2, 3.3); } ..*•' , :Ш. Это означает, что мы получаем возможность управлять поведением функций, передавая им в качестве параметров делегатов функций, что во многом напоминает выбор используемого (snap-in). Например, может иметься функция, используемая для сортировки строкового массива по алфавиту. Существуют различные алгорит- мы сортировки списков, обладающие различным быстродействием, что зависит от характеристик сортируемого списка. С помощью делегатов мы получаем возмож- ность определять метод, который необходимо использовать, передавая функции, выполняющей сортировку, делегата той функции, в которой реализован соответст- вующий алгоритм сортировки. Вариантов применения делегатов существует очень много, но, как отмечалось выше, наиболее плодотворно они используются при обработке событий. Мы будем обсуждать эту тему в главе 12. Итоги В данной главе был сделан достаточно полный обзор использования функций в программе на С#. Большая часть дополнительных возможностей, предоставляемых функциями (в частности, использование делегатов), являются достаточно абстракт- ными, и их необходимо обсуждать только с точки зрения объектно-ориентированного программирования, к рассмотрению которого мы вскоре перейдем. Подведем итоги темам, которые обсуждались в данной главе: • Определение и использование функций в консольных приложениях • Обмен данными с функциями посредством параметров и возвращаемых значений • Массивы параметров • Передача параметров по ссылке и по значению • Использование выходных параметров для дополнительных возвращаемых значений • Понятие области действия переменной • Особенности функции Main (), в том числе использование параметров командной строки • Использование функций в типах структур • Перегрузка функций • Делегаты 132 Упражнения 1. В следующих двух функциях содержатся ошибки. Какие именно? s t a t i c bool Write() Console.WriteLine(*Text output from function."); s t a t i c void myFunction(string label, params i n t [ ] args, bool showLabel) i f (showLabel) Console.WriteLine(label); foreach {int i in args) : • •/. ' . . Console.WriteLine("{О}*, x) ; • : 2. Напишите приложение, которое использует два аргумента командной строки для присваивания значений строковой и целой переменным соответственно, а затем выводит эти значения. 3. Создайте делегата и используйте его вместо функции Console.ReadLineо в запросе ввода от пользователя. 4. Модифицируйте приведенную ниже структуру, включив в нее функцию, которая возвращает суммарную стоимость заказа: struct order // заказ // public string iteraName; // наименование // public int unitCount; // число единиц // public double unitCost; // стоимость одной единиц
|