Текст книги "Полное руководство. С# 4.0"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 29 (всего у книги 58 страниц)
ГЛАВА 15. Делегаты, события и лямбда-выражения
В этой главе рассматриваются три новых средства С#: делегаты, события и лямбда-выражения. Делегат предоставляет возможность инкапсулировать метод, а событие уведомляет о том, что произошло некоторое действие. Делегаты и события тесно связаны друг с другом, поскольку событие основывается на делегате. Оба средства расширяют круг прикладных задача, решаемых при про граммировании на С#. А лямбда-выражение представляет собой новое синтаксическое средство, обеспечивающее упрощенный, но в то же время эффективный способ опре деления того, что по сути является единицей исполняемого кода. Лямбда-выражения обычно служат для работы с деле гатами и событиями, поскольку делегат может ссылаться на лямбда-выражение. (Кроме того, лямбда-выражения очень важны для языка LINQ, описываемого в главе 19.) В данной главе рассматриваются также анонимные методы, ковари антность, контравариантность и групповые преобразования методов. Делегаты
Начнем с определения понятия делегата. Попросту го воря, делегат представляет собой объект, который может ссылаться на метод. Следовательно, когда создается делегат, то в итоге получается объект, содержащий ссылку на метод. Более того, метод можно вызывать по этой ссылке. Иными словами, делегат позволяет вызывать метод, на который он ссылается. Ниже будет показано, насколько действенным оказывается такой принцип.
Следует особо подчеркнуть, что один и тот же делегат может быть использован для вызова разных методов во время выполнения программы, для чего достаточно изменить метод, на который ссылается делегат. Таким образом, метод, вызываемый делегатом, определяется во время выполнения, а не в процессе компиляции. В этом, собственно, и заключается главное преимущество делегата.
ПРИМЕЧАНИЕ Если у вас имеется опыт программирования на C/C++, то вам полезно будет знать, что делегат в C# подобен указателю на функцию в C/C++.
Тип делегата объявляется с помощью ключевого слова delegate. Ниже приведена общая форма объявления делегата: delegate возвращаемый_тип имя(список_параметров);
где возвращаемыйтип обозначает тип значения, возвращаемого методами, которые будут вызываться делегатом; имя – конкретное имя делегата; списокпараметров – параметры, необходимые для методов, вызываемых делегатом. Как только будет соз дан экземпляр делегата, он может вызывать и ссылаться на те методы, возвращаемый тип и параметры которых соответствуют указанным в объявлении делегата.
Самое главное, что делегат может служить для вызова любого метода с соответству ющей сигнатурой и возвращаемым типом. Более того, вызываемый метод может быть методом экземпляра, связанным с отдельным объектом, или же статическим методом, связанным с конкретным классом. Значение имеет лишь одно: возвращаемый тип и сигнатура метода должны быть согласованы с теми, которые указаны в объявлении делегата.
Для того чтобы показать делегат в действии, рассмотрим для начала простой при мер его применения. // Простой пример применения делегата. using System; // Объявить тип делегата. delegate string StrMod(string str); class DelegateTest { // Заменить пробелы дефисами. static string ReplaceSpaces(string s) { Console.WriteLine("Замена пробелов дефисами."); return s.Replace(' ', '-'); } // Удалить пробелы. static 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; } // Обратить строку. static 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; } static void Main() { // Сконструировать делегат. StrMod strOp = new StrMod(ReplaceSpaces); string str; // Вызвать методы с помощью делегата. str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine(); strOp = new StrMod(RemoveSpaces); str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine(); strOp = new StrMod(Reverse); str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); } }
Вот к какому результату приводит выполнение этого кода. Замена пробелов дефисами. Результирующая строка: Это-простой-тест. Удаление пробелов. Результирующая строка: Этопростойтест. Обращение строки. Результирующая строка: .тсет йотсорп отЭ
Рассмотрим данный пример более подробно. В его коде сначала объявляется деле гат StrMod типа string, как показано ниже. delegate string StrMod(string str);
Как видите, делегат StrMod принимает один параметр типа string и возвращает одно значение того же типа.
Далее в классе DelegateTest объявляются три статических метода с одним пара метром типа string и возвращаемым значением того же типа. Следовательно, они со ответствуют делегату StrMod. Эти методы видоизменяют строку в той или иной фор ме. Обратите внимание на то, что в методе ReplaceSpaces() для замены пробелов дефисами используется один из методов типа string – Replace().
В методе Main() создается переменная экземпляра strOp ссылочного типа StrMod и затем ей присваивается ссылка на метод ReplaceSpaces(). Обратите особое внима ние на следующую строку кода. StrMod strOp = new StrMod(ReplaceSpaces);
В этой строке метод 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. На этот раз обращает ся строка с удаленными пробелами.
Цепочки вызовов являются весьма эффективным механизмом, поскольку они по зволяют определить ряд методов, выполняемых единым блоком. Благодаря этому улучшается структура некоторых видов кода. Кроме того, цепочки вызовов имеют осо бое значение для обработки событий, как станет ясно в дальнейшем. Ковариантность и контравариантность
Делегаты становятся еще более гибкими средствами программирования благодаря двум свойствам: ковариантности и контравариантности. Как правило, метод, передава емый делегату, должен иметь такой же возвращаемый тип и сигнатуру, как и делегат. Но в отношении производных типов это правило оказывается не таким строгим бла годаря ковариантности и контравариантности. В частности, ковариантность позволяет присвоить делегату метод, возвращаемым типом которого служит класс, производный от класса, указываемого в возвращаемом типе делегата. А контравариантность позво ляет присвоить делегату метод, типом параметра которого служит класс, являющийся базовым для класса, указываемого в объявлении делегата.
Ниже приведен пример, демонстрирующий ковариантность и контравариант ность. // Продемонстрировать ковариантность и контравариантность. using System; class X { public int Val; } // Класс Y, производный от класса X. class Y : X { } // Этот делегат возвращает объект класса X и // принимает объект класса Y в качестве аргумента. delegate X ChangeIt(Y obj); class CoContraVariance { // Этот метод возвращает объект класса X и // имеет объект класса X в качестве параметра. static X IncrA(X obj) { X temp = new X(); temp.Val = obj.Val + 1; return temp; } // Этот метод возвращает объект класса Y и // имеет объект класса Y в качестве параметра. static Y IncrB(Y obj) { Y temp = new Y(); temp.Val = obj.Val + 1; return temp; } static void Main() { Y Yob = new Y(); // В данном случае параметром метода IncrA является объект класса X, // а параметром делегата ChangeIt – объект класса Y. Но благодаря // контравариантности следующая строка кода вполне допустима. Changelt change = IncrA; X Xob = change(Yob); Console.WriteLine("Xob: " + Xob.Val); // В этом случае возвращаемым типом метода IncrB служит объект класса Y, // а возвращаемым типом делегата ChangeIt – объект класса X. Но благодаря // ковариантности следующая строка кода оказывается вполне допустимой. change = IncrB; Yob = (Y) change (Yob); Console.WriteLine("Yob: " + Yob.Val); } }
Вот к какому результату приводит выполнение этого кода. Xob: 1 Yob: 1
В данном примере класс Y является производным от класса X. А делегат ChangeIt объявляется следующим образом. delegate X ChangeIt(Y obj);
Делегат возвращает объект класса X и принимает в качестве параметра объект клас са Y. А методы IncrA() и IncrB() объявляются следующим образом. static X IncrA(X obj) static Y IncrB(Y obj)
Метод IncrA() принимает объект класса X в качестве параметра и возвращает объект того же класса. А метод IncrB() принимает в качестве параметра объект клас са Y и возвращает объект того же класса. Но благодаря ковариантности и контравари антности любой из этих методов может быть передан делегату ChangeIt, что и демон стрирует рассматриваемый здесь пример.
Таким образом, в строке ChangeIt change = IncrA;
метод IncrA() может быть передан делегату благодаря контравариантности, так как объект класса X служит в качестве параметра метода IncrA(), а объект класса Y – в качестве параметра делегата ChangeIt. Но метод и делегат оказываются совмести мыми в силу контравариантности, поскольку типом параметра метода, передаваемого делегату, служит класс, являющийся базовым для класса, указываемого в качестве типа параметра делегата.
Приведенная ниже строка кода также является вполне допустимой, но на этот раз благодаря ковариантности. change = IncrB;
В данном случае возвращаемым типом для метода IncrB() служит класс Y, а для делегата – класс X. Но поскольку возвращаемый тип метода является производным классом от возвращаемого типа делегата, то оба оказываются совместимыми в силу ковариантности. Класс System.Delegate
Все делегаты и классы оказываются производными неявным образом от класса System.Delegate. Как правило, членами этого класса не пользуются непосред ственно, и это не делается явным образом в данной книге. Но члены класса System. Delegate могут оказаться полезными в ряде особых случаев. Назначение делегатов
В предыдущих примерах был наглядно продемонстрирован внутренний механизм действия делегатов, но эти примеры не показывают их истинное назначение. Как пра вило, делегаты применяются по двум причинам. Во-первых, как упоминалось ранее в этой главе, делегаты поддерживают события. И во-вторых, делегаты позволяют вы зывать методы во время выполнения программы, не зная о них ничего определенно го в ходе компиляции. Это очень удобно для создания базовой конструкции, допу скающей подключение отдельных программных компонентов. Рассмотрим в качестве примера графическую программу, аналогичную стандартной сервисной программе Windows Paint. С помощью делегата можно предоставить пользователю возможность подключать специальные цветные фильтры или анализаторы изображений. Кроме того, пользователь может составлять из этих фильтров или анализаторов целые по следовательности. Подобные возможности программы нетрудно обеспечить, исполь зуя делегаты. Анонимные функции
Метод, на который ссылается делегат, нередко используется только для этой цели. Иными словами, единственным основанием для существования метода служит то об стоятельство, что он может быть вызван посредством делегата, но сам он не вызывается вообще. В подобных случаях можно воспользоваться анонимной функцией, чтобы не создавать отдельный метод. Анонимная функция, по существу, представляет собой безымянный кодовый блок, передаваемый конструктору делегата. Преимущество ано нимной функции состоит, в частности, в ее простоте. Благодаря ей отпадает необходи мость объявлять отдельный метод, единственное назначение которого состоит в том, что он передается делегату.
Начиная с версии 3.0, в C# предусмотрены две разновидности анонимных функ ций: анонимные методы и лямбда-выражения. Анонимные методы были внедрены в C# еще в версии 2.0, а лямбда-выражения – в версии 3.0. В целом лямбда-выражение со вершенствует принцип действия анонимного метода и в настоящее время считается более предпочтительным для создания анонимной функции. Но анонимные методы широко применяются в существующем коде С# и поэтому по-прежнему являются важной составной частью С#. А поскольку анонимные методы предшествовали по явлению лямбда-выражений, то ясное представление о них позволяет лучше понять особенности лямбда-выражений. К тому же анонимные методы могут быть использо ваны в целом ряде случаев, где применение лямбда-выражений оказывается невозмож ным. Именно поэтому в этой главе рассматриваются и анонимные методы, и лямбда– выражения. Анонимные методы
Анонимный метод – один из способов создания безымянного блока кода, связан ного с конкретным экземпляром делегата. Для создания анонимного метода достаточ но указать кодовый блок после ключевого слова delegate. Покажем, как это делается, на конкретном примере. В приведенной ниже программе анонимный метод служит для подсчета от 0 до 5. // Продемонстрировать применение анонимного метода. using System; // Объявить тип делегата. delegate void CountIt(); class AnonMethDemo { static void Main() { // Далее следует код для подсчета чисел, передаваемый делегату // в качестве анонимного метода. CountIt count = delegate { // Этот кодовый блок передается делегату. for(int i=0; i <= 5; i++) Console.WriteLine(i); }; // обратите внимание на точку с запятой count(); } }
В данной программе сначала объявляется тип делегата CountIt без параметров и с возвращаемым типом void. Далее в методе Main() создается экземпляр count делегата CountIt, которому передается кодовый блок, следующий после ключевого слова delegate. Именно этот кодовый блок и является анонимным методом, кото рый будет выполняться при обращении к делегату count. Обратите внимание на то, что после кодового блока следует точка с запятой, фактически завершающая оператор объявления. Ниже приведен результат выполнения данной программы. 0 1 2 3 4 5 Передача аргументов анонимному методу
Анонимному методу можно передать один или несколько аргументов. Для этого достаточно указать в скобках список параметров после ключевого слова delegate, а при обращении к экземпляру делегата – передать ему соответствующие аргумен ты. В качестве примера ниже приведен вариант предыдущей программы, измененный с целью передать в качестве аргумента конечное значение для подсчета. // Продемонстрировать применение анонимного метода, принимающего аргумент. using System; // Обратите внимание на то, что теперь у делегата CountIt имеется параметр. delegate void CountIt (int end); class AnonMethDemo2 { static void Main() { // Здесь конечное значение для подсчета передается анонимному методу. CountIt count = delegate (int end) { for(int i=0; i <= end; i++) Console.WriteLine(i); }; count(3); Console.WriteLine(); count(5); } }
В этом варианте программы делегат CountIt принимает целочисленный аргумент. Обратите внимание на то, что при создании анонимного метода список параметров указывается после ключевого слова delegate. Параметр end становится доступным для кода в анонимном методе таким же образом, как и при создании именованного метода. Ниже приведен результат выполнения данной программы. 0 1 2 3 0 1 2 3 4 5 Возврат значения из анонимного метода
Анонимный метод может возвращать значение. Для этой цели служит оператор return, действующий в анонимном методе таким же образом, как и в именованном методе. Как и следовало ожидать, тип возвращаемого значения должен быть совме стим с возвращаемым типом, указываемым в объявлении делегата. В качестве при мера ниже приведен код, выполняющий подсчет с суммированием и возвращающий результат. // Продемонстрировать применение анонимного метода, возвращающего значение. using System; // Этот делегат возвращает значение. delegate int CountIt(int end); class AnonMethDemo3 { static void Main() { int result; // Здесь конечное значение для подсчета перелается анонимному методу. // А возвращается сумма подсчитанных чисел. CountIt count = delegate (int end) { int sum = 0; for(int i=0; i <= end; i++) { Console.WriteLine (i); sum += i; } return sum; // возвратить значение из анонимного метода }; result = count(3); Console.WriteLine("Сумма 3 равна " + result); Console.WriteLine(); result = count (5); Console.WriteLine("Сумма 5 равна " + result); } }
В этом варианте кода суммарное значение возвращается кодовым блоком, связан ным с экземпляром делегата count. Обратите внимание на то, что оператор return применяется в анонимном методе таким же образом, как и в именованном методе. Ниже приведен результат выполнения данного кода. 0 1 2 3 Сумма 3 равна 6 0 1 2 3 4 5 Сумма 5 равна 15 Применение внешних переменных в анонимных методах
Локальная переменная, в область действия которой входит анонимный метод, на зывается внешней переменной. Такие переменные доступны для использования в ано нимном методе. И в этом случае внешняя переменная считается захваченной. Захвачен ная переменная существует до тех пор, пока захвативший ее делегат не будет собран в "мусор". Поэтому если локальная переменная, которая обычно прекращает свое су ществование после выхода из кодового блока, используется в анонимном методе, то она продолжает существовать до тех пор, пока не будет уничтожен делегат, ссылаю щийся на этот метод.
Захват локальной переменной может привести к неожиданным результатам. В ка честве примера рассмотрим еще один вариант программы подсчета с суммированием чисел. В данном варианте объект CountIt конструируется и возвращается статическим методом Counter(). Этот объект использует переменную sum, объявленную в охваты вающей области действия метода Counter(), а не самого анонимного метода. Поэто му переменная sum захватывается анонимным методом. Метод Counter() вызывается в методе Main() для получения объекта CountIt, а следовательно, переменная sum не уничтожается до самого конца программы. // Продемонстрировать применение захваченной переменной. using System; // Этот делегат возвращает значение типа int и принимает аргумент типа int. delegate int CountIt(int end); class VarCapture { static CountIt Counter() { int sum = 0; // Здесь подсчитанная сумма сохраняется в переменной sum. CountIt ctObj = delegate (int end) { for(int i=0; i <= end; i++) { Console.WriteLine(i); sum += i; } return sum; }; return ctObj; } static void Main() { // Получить результат подсчета. CountIt count = Counter(); int result; result = count(3); Console.WriteLine("Сумма 3 равна " + result); Console.WriteLine(); result = count (5); Console.WriteLine("Сумма 5 равна " + result); } }
Ниже приведен результат выполнения этой программы. Обратите особое внима ние на суммарное значение. 0 1 2 3 Сумма 3 равна 6 0 1 2 3 4 5 Сумма 5 равна 21
Как видите, подсчет по-прежнему выполняется как обычно. Но обратите внимание на то, что сумма 5 теперь равна 21, а не 15! Дело в том, что переменная sum захваты вается объектом ctObj при его создании в методе Counter(). Это означает, что она продолжает существовать вплоть до уничтожения делегата count при "сборке мусо ра" в самом конце программы. Следовательно, ее значение не уничтожается после воз врата из метода Counter() или при каждом вызове анонимного метода, когда проис ходит обращение к делегату count в методе Main().
Несмотря на то что применение захваченных переменных может привести к до вольно неожиданным результатам, как в приведенном выше примере, оно все же логически обоснованно. Ведь когда анонимный метод захватывает переменную, она продолжает существовать до тех пор, пока используется захватывающий ее делегат. В противном случае захваченная переменная оказалась бы неопределенной, когда она могла бы потребоваться делегату. Лямбда-выражения
Несмотря на всю ценность анонимных методов, им на смену пришел более со вершенный подход: лямбда-выражение. Не будет преувеличением сказать, что лямбда– выражение относится к одним из самых важных нововведений в С#, начиная с выпуска исходной версии 1.0 этого языка программирования. Лямбда-выражение основывается на совершенно новом синтаксическом элементе и служит более эффективной альтер нативой анонимному методу. И хотя лямбда-выражения находят применение главным образом в работе с LINQ (подробнее об этом – в главе 19), они часто используются и вместе с делегатами и событиями. Именно об этом применении лямбда-выражений и пойдет речь в данном разделе.
Лямбда-выражение – это другой собой создания анонимной функции. (Первый ее способ, анонимный метод, был рассмотрен в предыдущем разделе.) Следовательно, лямбда-выражение может быть присвоено делегату. А поскольку лямбда-выражение считается более эффективным, чем эквивалентный ему анонимный метод то в боль шинстве случаев рекомендуется отдавать предпочтение именно ему. Лямбда-оператор
Во всех лямбда-выражениях применяется новый лямбда-оператор =>, который раз деляет лямбда-выражение на две части. В левой его части указывается входной пара метр (или несколько параметров), а в правой части – тело лямбда-выражения. Опера тор => иногда описывается такими словами, как "переходит" или "становится".
В C# поддерживаются две разновидности лямбда-выражений в зависимости от тела самого лямбда-выражения. Так, если тело лямбда-выражения состоит из одного вы ражения, то образуется одиночное лямбда-выражение. В этом случае тело выражения не заключается в фигурные скобки. Если же тело лямбда-выражения состоит из блока операторов, заключенных в фигурные скобки, то образуется блочное лямбда-выражение. При этом блочное лямбда-выражение может содержать целый ряд операторов, в том числе циклы, вызовы методов и условные операторы if. Обе разновидности лямбда– выражений рассматриваются далее по отдельности. Одиночные лямбда-выражения
В одиночном лямбда-выражении часть, находящаяся справа от оператора =>, воз действует на параметр (или ряд параметров), указываемый слева. Возвращаемым результатом вычисления такого выражения является результат выполнения лямбда– оператора.
Ниже приведена общая форма одиночного лямбда-выражения, принимающего единственный параметр. параметр => выражение
Если же требуется указать несколько параметров, то используется следующая форма. (список_параметров) => выражение
Таким образом, когда требуется указать два параметра или более, их следует за ключить в скобки. Если же выражение не требует параметров, то следует использовать пустые скобки.