Полное руководство. С# 4.0
Шрифт:
В этой строке метод ReplaceSpaces передается в качестве параметра. При этом указывается только его имя, но не параметры. Данный пример можно обобщить: при получении экземпляра делегата достаточно указать только имя метода, на который должен ссылаться делегат. Ясно, что сигнатура метода должна совпадать с той, что указана в объявлении делегата. В противном случае во время компиляции возникнет ошибка.
Далее метод ReplaceSpaces вызывается с помощью экземпляра делегата strOp, как показано ниже. str = strOp("Это простой тест.");
Экземпляр делегата strOp ссылается на метод ReplaceSpaces, и поэтому вы зывается именно этот метод.
Затем экземпляру делегата strOp присваивается ссылка на метод RemoveSpaces, и с его помощью вновь вызывается указанный метод — на этот раз RemoveSpaces. И наконец, экземпляру делегата strOp присваивается ссылка на метод Reverse.
Главный вывод из данного примера заключается в следующем: в тот момент, когда происходит обращение к экземпляру делегата strOp, вызывается метод, на который он ссылается. Следовательно, вызов метода разрешается во время выполнения, а не в процессе компиляции. Групповое преобразование делегируемых методов
Еще в версии C# 2.0 было внедрено специальное средство, существенно упрощаю щее синтаксис присваивания метода делегату. Это так называемое групповое преобразо вание методов, позволяющее присвоить имя метода делегату, не прибегая к оператору new или явному вызову конструктора делегата.
Ниже приведен метод Main из предыдущего примера, измененный с целью про демонстрировать групповое преобразование методов. static void Main { // Сконструировать делегат, используя групповое преобразование методов. StrMod strOp = ReplaceSpaces; // использовать групповое преобразование методов string str; // Вызвать методы с помощью делегата, str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; strOp = RemoveSpaces; // использовать групповое преобразование методов str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; strOp = Reverse; // использовать групповое преобразование методов str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; }
Обратите особое внимание на то, как создается экземпляр делегата strOp и как ему присваивается метод ReplaceSpaces в следующей строке кода. strOp = RemoveSpaces; // использовать групповое преобразование методов
В этой строке кода имя метода присваивается непосредственно экземпляру деле гата strOp, а все заботы по автоматическому преобразованию метода в тип делегата "возлагаются" на средства С#. Этот синтаксис может быть распространен на любую ситуацию, в которой метод присваивается или преобразуется в тип делегата.
Синтаксис группового преобразования методов существенно упрощен по сравне нию с прежним подходом к делегированию, поэтому в остальной части книги исполь зуется именно он. Применение методов экземпляра в качестве делегатов
В предыдущем примере использовались статические методы, но делегат может ссылаться и на методы экземпляра, хотя для этого требуется ссылка на объект. Так, ниже приведен измененный вариант предыдущего примера, в котором операции со строками инкапсулируются в классе StringOps. Следует заметить, что в данном слу чае может быть также использован синтаксис группового преобразования методов. // Делегаты могут ссылаться и на методы экземпляра. using System; // Объявить тип делегата. delegate string StrMod(string str); class StringOps { // Заменить пробелы дефисами. public string ReplaceSpaces (string s) { Console.WriteLine("Замена пробелов дефисами."); return s.Replace(' ', '-'); } // Удалить пробелы. public string RemoveSpaces(string s) { string temp = ""; int i; Console.WriteLine("Удаление пробелов."); for(i=0; i < s.Length; i++) if(s[i] != ' ') temp += s[i]; return temp; } // Обратить строку. public string Reverse(string s) { string temp = ""; int i, j; Console.WriteLine("Обращение строки."); for(j=0, i=s.Length-1; i >= 0; i--, j++) temp += s[i]; return temp; } } class DelegateTest { static void Main { StringOps so = new StringOps; // создать экземпляр // объекта класса StringOps // Инициализировать делегат. StrMod strOp = so.ReplaceSpaces; string str; // Вызвать методы с помощью делегатов. str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; strOp = so.RemoveSpaces; str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; strOp = so.Reverse; str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); } }
Результат выполнения этого кода получается таким же, как и в предыдущем при мере, но на этот раз делегат обращается к методам по ссылке на экземпляр объекта класса StringOps. Групповая адресация
Одним из самых примечательных свойств делегата является поддержка групповой адресации. Попросту говоря, групповая адресация — это возможность создать список, или цепочку вызовов, для методов, которые вызываются автоматически при обращении к делегату. Создать такую цепочку нетрудно. Для этого достаточно получить экзем пляр делегата, а затем добавить методы в цепочку с помощью оператора + или +=. Для удаления метода из цепочки служит оператор - или -=. Если делегат возвращает значение, то им становится значение, возвращаемое последним методом в списке вы зовов. Поэтому делегат, в котором используется групповая адресация, обычно имеет возвращаемый тип void.
Ниже приведен пример групповой адресации. Это переработанный вариант предыдущих примеров, в котором тип значений, возвращаемых методами манипули рования строками, изменен на void, а для возврата измененной строки в вызывающую часть кода служит параметр типа ref. Благодаря этому методы оказываются более приспособленными для групповой адресации. // Продемонстрировать групповую адресацию. using System; // Объявить тип делегата. delegate void StrMod(ref string str); class MultiCastDemo { // Заменить пробелы дефисами. static void ReplaceSpaces(ref string s) { Console.WriteLine("Замена пробелов дефисами."); s = s.Replace(' ', '-'); } // Удалить пробелы. static void RemoveSpaces(ref string s) { string temp = ""; int i; Console.WriteLine("Удаление пробелов."); for(i=0; i < s.Length; i++) if(s[i] != ' ') temp += s[i]; s = temp; } // Обратить строку. static void Reverse(ref string s) { string temp = ""; int i, j; Console.WriteLine("Обращение строки."); for(j=0, i=s.Length-1; i >= 0; i--, j++) temp += s[i]; s = temp; } static void Main { // Сконструировать делегаты. StrMod strOp; StrMod replaceSp = ReplaceSpaces; StrMod removeSp = RemoveSpaces; StrMod reverseStr = Reverse; string str = "Это простой тест."; // Организовать групповую адресацию. strOp = replaceSp; strOp += reverseStr; // Обратиться к делегату с групповой адресацией. strOp(ref str); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; // Удалить метод замены пробелов и добавить метод удаления пробелов. strOp -= replaceSp; strOp += removeSp; str = "Это простой тест."; // восстановить исходную строку // Обратиться к делегату с групповой адресацией. strOp (ref str); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine; } }
Выполнение этого кода приводит к следующему результату. Замена пробелов дефисами. Обращение строки. Результирующая строка: .тсет-йотсорп-отЭ Обращение строки. Удаление пробелов. Результирующая строка: .тсетйотсорпотЭ
В методе Main из рассматриваемого здесь примера кода создаются четыре экзем пляра делегата. Первый из них, strOp, является пустым, а три остальных ссылаются на конкретные методы видоизменения строки. Затем организуется групповая адресация для вызова методов RemoveSpaces и Reverse. Это делается в приведенных ниже строках кода. strOp = replaceSp; strOp += reverseStr
Сначала делегату strOp присваивается ссылка replaceSp, а затем с помощью опе ратора += добавляется ссылка reverseStr. При обращении к делегату strOp вызы ваются оба метода, заменяя пробелы дефисами и обращая строку, как и показывает приведенный выше результат.
Далее ссылка replaceSp удаляется из цепочки вызовов в следующей строке кода: strOp -= replaceSp;
и добавляется ссылка removeSp в строке кода. strOp += removeSp;
После этого вновь происходит обращение к делегату strOp. На этот раз обращает ся строка с удаленными пробелами.
Цепочки вызовов являются весьма эффективным механизмом, поскольку они по зволяют определить ряд методов, выполняемых единым блоком. Благодаря этому улучшается структура некоторых видов кода. Кроме того, цепочки вызовов имеют осо бое значение для обработки событий, как станет ясно в дальнейшем. Ковариантность и контравариантность
Делегаты становятся еще более гибкими средствами программирования благодаря двум свойствам: ковариантности и контравариантности. Как правило, метод, передава емый делегату, должен иметь такой же возвращаемый тип и сигнатуру, как и делегат. Но в отношении производных типов это правило оказывается не таким строгим бла годаря ковариантности и контравариантности. В частности, ковариантность позволяет присвоить делегату метод, возвращаемым типом которого служит класс, производный от класса, указываемого в возвращаемом типе делегата. А контравариантность позво ляет присвоить делегату метод, типом параметра которого служит класс, являющийся базовым для класса, указываемого в объявлении делегата.