программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Скачать 3.32 Mb.
|
Ниже приведен пример, демонстрирующий ковариантность и контравариантность. // Продемонстрировать ковариантность и контравариантность. 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, // а параметром делегата Changelt – объект класса Y. Но благодаря // контравариантности следующая строка кода вполне допустима. Changelt change = IncrA; X Xob = change(Yob); Console.WriteLine("Xob: " + Xob.Val); // В этом случае возвращаемым типом метода IncrB служит объект класса Y, // а возвращаемым типом делегата Changelt – объект класса X. Но благодаря // ковариантности следующая строка кода оказывается вполне допустимой, change = IncrB; Yob = (Y) change(Yob); Console.WriteLine("Yob: " + Yob.Val); } } Вот к какому результату приводит выполнение этого кода. Xob: 1 Yob: 1 В данном примере класс Y является производным от класса X. А делегат Changelt объявляется следующим образом. delegate X Changelt(Y obj); Делегат возвращает объект класса X и принимает в качестве параметра объект класса Y. А методы IncrA () и IncrB () объявляются следующим образом. static X IncrA(X obj) static Y IncrB(Y obj) Метод IncrA () принимает объект класса X в качестве параметра и возвращает объект того же класса. А метод IncrB () принимает в качестве параметра объект класса Y и возвращает объект того же класса. Но благодаря ковариантности и контравари‑антности любой из этих методов может быть передан делегату Changelt, что и демонстрирует рассматриваемый здесь пример. Таким образом, в строке Changelt change = IncrA; метод IncrA () может быть передан делегату благодаря контравариантности, так как объект класса X служит в качестве параметра метода IncrA () , а объект класса Y – в качестве параметра делегата Changelt. Но метод и делегат оказываются совместимыми в силу контравариантности, поскольку типом параметра метода, передаваемого делегату, служит класс, являющийся базовым для класса, указываемого в качестве типа параметра делегата. Приведенная ниже строка кода также является вполне допустимой, но на этот раз благодаря ковариантности. 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 Countlt(); class AnonMethDemo { static void Main() { // Далее следует код для подсчета чисел, передаваемый делегату // в качестве анонимного метода. Countlt count = delegate { // Этот кодовый блок передается делегату, for (int i=0; i <= 5; i++) Console.WriteLine(i) ; }; // обратите внимание на точку с запятой count(); } } В данной программе сначала объявляется тип делегата Countlt без параметров и с возвращаемым типом void. Далее в методе Main () создается экземпляр count делегата Countlt, которому передается кодовый блок, следующий после ключевого слова delegate. Именно этот кодовый блок и является анонимным методом, который будет выполняться при обращении к делегату count. Обратите внимание на то, что после кодового блока следует точка с запятой, фактически завершающая оператор объявления. Ниже приведен результат выполнения данной программы. 0 1 2 3 4 5 Передача аргументов анонимному методу Анонимному методу можно передать один или несколько аргументов. Для этого достаточно указать в скобках список параметров после ключевого слова delegate, а при обращении к экземпляру делегата – передать ему соответствующие аргументы. В качестве примера ниже приведен вариант предыдущей программы, измененный с целью передать в качестве аргумента конечное значение для подсчета. // Продемонстрировать применение анонимного метода, принимающего аргумент, using System; // Обратите внимание на то, что теперь у делегата Countlt имеется параметр, delegate void Countlt(int end); class AnonMethDemo2 { static void Main() { // Здесь конечное значение для подсчета передается анонимному методу. Countlt count = delegate (int end) { for(int i=0; i <= end; i++) Console.WriteLine(i); }; count (3); Console.WriteLine (); count (5); } } В этом варианте программы делегат Countlt принимает целочисленный аргумент. Обратите внимание на то, что при создании анонимного метода список параметров указывается после ключевого слова delegate. Параметр end становится доступным для кода в анонимном методе таким же образом, как и при создании именованного метода. Ниже приведен результат выполнения данной программы. 0 1 2 3 0 1 2 3 4 5 Возврат значения из анонимного метода Анонимный метод может возвращать значение. Для этой цели служит оператор return, действующий в анонимном методе таким же образом, как и в именованном методе. Как и следовало ожидать, тий возвращаемого значения должен быть совместим с возвращаемым типом, указываемым в объявлении делегата. В качестве примера ниже приведен код, выполняющий подсчет с суммированием и возвращающий результат. // Продемонстрировать применение анонимного метода, возвращающего значение. // Этот делегат возвращает значение, delegate int Countlt(int end); class AnonMethDemo3 { static void Main() { int result; // Здесь конечное значение для подсчета передается анонимному методу. //А возвращается сумма подсчитанных чисел. Countlt 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 Применение внешних переменных в анонимных методах Локальная переменная, в область действия которой входит анонимТный метод, называется внешней переменной. Такие переменные доступны для использования в анонимном методе. И в этом случае внешняя переменная считается захваченной. Захваченная переменная существует до тех пор, пока захвативший ее делегат не будет собран в "мусор". Поэтому если локальная переменная, которая обычно прекращает свое существование после выхода из кодового блока, используется в анонимном методе, то она продолжает существовать до тех пор, пока не будет уничтожен делегат, ссылающийся на этот метод. Захват локальной переменной может привести к неожиданным результатам. В качестве примера рассмотрим еще один вариант программы подсчета с суммированием чисел. В данном варианте объект Countlt конструируется и возвращается статическим методом Counter () . Этот объект использует переменную sum, объявленную в охватывающей области действия метода Counter () , а не самого анонимного метода. Поэтому переменная sum захватывается анонимным методом. Метод Counter () вызывается в методе Main () для получения объекта Countlt, а следовательно, переменная sum не уничтожается до самого конца программы. // Продемонстрировать применение захваченной переменной, using System; // Этот делегат возвращает значение типа int и принимает аргумент типа int. delegate int Countlt(int end); class VarCapture { static Countlt Counter () { int sum = 0; // Здесь подсчитанная сумма сохраняется в переменной sum. Countlt ctObj = delegate (int end) { for(int i=0; i <= end; i++) { Console.WriteLine(i); sum += i; } return sum; }; return ctObj; } static void Main() { // Получить результат подсчета. Countlt 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 захватывается объектом ctOb j при его создании в методе Counter () . Это означает, что она продолжает существовать вплоть до уничтожения делегата count при "сборке мусо‑ра" в самом конце программы. Следовательно, ее значение не уничтожается после возврата из метода Counter () или при каждом вызове анонимного метода, когда происходит обращение к делегату count в методе Main () . Несмотря на то что применение захваченных переменных может привести к довольно неожиданным результатам, как в приведенном выше примере, оно все же логически обоснованно. Ведь когда анонимный метод захватывает переменную, она продолжает существовать до тех пор, пока используется захватывающий ее делегат. В противном случае захваченная переменная оказалась бы неопределенной, когда она могла бы потребоваться делегату. Лямбда‑выражения Несмотря на всю ценность анонимных методов, им на смену пришел более совершенный подход: лямбда‑выражение. Не будет преувеличением сказать, что лямбда‑выражение относится к одним из самых важных нововведений в С#, начиная с выпуска исходной версии 1.0 этого языка программирования. Лямбда‑выражение основывается на совершенно новом синтаксическом элементе и служит более эффективной альтернативой анонимному методу. И хотя лямбда‑выражения находят применение главным образом в работе с LINQ (подробнее об этом – в главе 19), они часто используются и вместе с делегатами и событиями. Именно об этом применении лямбда‑выражений и пойдет речь в данном разделе. Лямбда‑выражение – это другой собой создания анонимной функции. (Первый ее способ, анонимный метод, был рассмотрен в предыдущем разделе.) Следовательно, лямбда‑выражение может быть присвоено делегату. А поскольку лямбда‑выражение считается более эффективным, чем эквивалентный ему анонимный метод, то в большинстве случаев рекомендуется отдавать предпочтение именно ему. Лямбда‑оператор Во всех лямбда‑выражениях применяется новый лямбда‑оператор =>, который разделяет лямбда‑выражение на две части. В левой его части указывается входной параметр (или несколько параметров), а в правой части – тело лямбда‑выражения. Оператор => иногда описывается такими словами, как "переходит" или "становится". В C# поддерживаются две разновидности лямбда‑выражений в зависимости от тела самого лямбда‑выражения. Так, если тело лямбда‑выражения состоит из одного выражения, то образуется одиночное лямбда‑выражение. В этом случае тело выражения не заключается в фигурные скобки. Если же тело лямбда‑выражения состоит из блока операторов, заключенных в фигурные скобки, то образуется блочное лямбда‑выражение. При этом блочное лямбда‑выражение может содержать целый ряд операторов, в том числе циклы, вызовы методов и условные операторы if. Обе разновидности лямбда‑выражений рассматриваются далее по отдельности. Одиночные лямбда‑выражения |