Васильев А.Н. Основы программирования на C#. Васильев А. Н. Программирование
Скачать 5.54 Mb.
|
ПОДРОБНОСТИ bПри вызове метода set() мы использовали анонимный метод, содержащий ссылку obj.symbol . Здесь нет ошибки, поскольку на момент вызова метода set() в переменную obj уже была записана ссылка на объект. В конструкторе, при создании объекта obj , мы не могли использовать ссылку obj.symbol . Причина очевидна сначала вычисляются аргументы конструктора, а уже затем выполняется его код. Ссылка на объект записывается в переменную obj после того, как объект создан. Поэтому при вычислении аргументов конструктора переменная obj еще не содержит ссылку на объект Делегаты и события 97 Анонимный метод может возвращаться в качестве результата другого метода. В листинге 2.9 поэтому поводу представлен пример Листинг 2.9. Анонимный метод как результат System; // Ɉɛɴɹɜɥɟɧɢɟ ɞɟɥɟɝɚɬɚ: delegate int MyDelegate(); // Ʉɥɚɫɫ ɫ ɝɥɚɜɧɵɦ ɦɟɬɨɞɨɦ: class AnonymAsResultDemo{ // ɋɬɚɬɢɱɟɫɤɢ ɦɟɬɨɞ ɪɟɡɭɥɶɬɚɬɨɦ ɜɨɡɜɪɚɳɚɟɬ ɫɫɵɥɤɭ ɧɚ // ɷɤɡɟɦɩɥɹɪ ɞɟɥɟɝɚɬɚ: static MyDelegate calculate(int n){ // Ʌɨɤɚɥɶɧɚɹ ɩɟɪɟɦɟɧɧɚɹ: int count=0; // Ɋɟɡɭɥɶɬɚɬ ɪɟɚɥɢɡɨɜɚɧ ɱɟɪɟɡ ɚɧɨɧɢɦɧɵɣ ɦɟɬɨɞ: return delegate(){ count+=n; return count; }; } // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ // ɉɟɪɟɦɟɧɧɚɹ ɬɢɩɚ ɞɟɥɟɝɚɬɚ: MyDelegate next=calculate(1); for(int i=1;i<=5;i++){ // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: Console.Write(next()+ Ǝ Ǝ); } Console.WriteLine(); // ɇɨɜɨɟ ɡɧɚɱɟɧɢɟ ɩɟɪɟɦɟɧɧɨɣ ɬɢɩɚ ɞɟɥɟɝɚɬɚ: next=calculate(3); for(int i=1;i<=5;i++){ Глава 2 98 // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: Console.Write(next()+ Ǝ Ǝ); } Console.WriteLine(); При выполнении программы получаем такой результат Результат выполнения программы (из листинга 2.9) 1 2 3 4 5 3 6 9 12 Делегат MyDelegate описан для создания экземпляров, которые могут ссылаться на методы без аргументов, возвращающие целочисленный результат. Еще мы описываем статический методу которого целочисленный аргумент (обозначен как n) и который результатом возвращает ссылку на экземпляр делегата MyDelegate. В теле метода объявлена локальная целочисленная переменная count с начальным нулевым значением. Далее следует инструкция и возвращаемым значением указано следующее выражение (после этого выражения в коде метода стоит точка с запятой count+=n; return Это код анонимного метода без аргументов, который результатом возвращает целое число. Этот анонимный метод возвращается результатом метода calculate(). q ПОДРОБНОСТИ bТип результата для метода calculate() указан как Формально это ссылка на экземпляр делегата MyDelegate . При вызове метода calculate() под результат метода выделяется переменная типа делегата MyDelegate . Результатом метод calculate() Делегаты и события 99 возвращает анонимный метод, который присваивается переменной, выделенной для записи результата. В итоге создается экземпляр делегата, который ссылается на анонимный метод. Ссылка на этот экземпляр записывается в переменную, выделенную для запоминания результата метода calculate() . Поэтому результатом метода calculate() является ссылка на экземпляр делегата MyDelegate , а этот экземпляр ссылается на упомянутый выше анонимный метод. В главном методе программы командой MyDelegate next=calculate(1) объявляется переменная next типа делегата MyDelegate , и значением ей присваивается результат вызова метода calculate() с аргументом 1. Затем с помощью оператора цикла несколько раз подряд вычисляется значение выражения next(). И каждый раз мы получаем новое значение (числа 1, 2, 3 итак далее. Почему так происходит При вызове метода calculate() с аргументом 1 создается локальная переменная count с начальным значением 0. Мы уже знаем, что метод calculate() возвращает результатом анонимный метод, который при вызове каждый раз увеличивает значение переменной (в данном случае на 1) и это новое значение возвращает как результат. В итоге получается так переменная next ссылается на экземпляр делегата, который ссылается на анонимный метода этот метод содержит ссылку на переменную count, созданную при вызове метода calculate() . После завершения вызова метода calculate() локальная переменная count должна бы удаляться. Но поскольку на эту переменную есть ссылка в анонимном методе, то она выживает. После первого вызова анонимного метода через переменную next значение переменной становится равным 1. При втором вызове она становится равной 2 итак далее. Поэтому при вычислении значения выражения) мы каждый раз получаем новое число (на единицу больше предыдущего). После выполнения команды next=calculate(3) в переменную next записывается ссылка на новый экземпляр делегата, ссылающийся на новый анонимный метод, который ссылается на новую локальную переменную. Она получила начальное значение 0 при вызове метода calculate() . И теперь каждый раз при вычислении значения выражения) мы получаем новое число, большее предыдущего на Еще одна программа немного напоминает предыдущую. Правда на этот раз последствия использования анонимного метода в качестве результата не такие экзотические. Обратимся к программному коду из листинга 2.10. Глава 2 100 Листинг 2.10. Реализация результата с помощью анонимного метода System; // Ɉɛɴɹɜɥɟɧɢɟ ɞɟɥɟɝɚɬɚ: delegate double Powers(double x); // Ƚɥɚɜɧɵɣ ɤɥɚɫɫ: class MoreAnonymAsResultDemo{ // ɋɬɚɬɢɱɟɫɤɢɣ ɦɟɬɨɞ ɪɟɡɭɥɶɬɚɬɨɦ ɜɨɡɜɪɚɳɚɟɬ ɫɫɵɥɤɭ // ɧɚ ɷɤɡɟɦɩɥɹɪ ɞɟɥɟɝɚɬɚ: static Powers maker(int n){ // Ʌɨɤɚɥɶɧɚɹ ɩɟɪɟɦɟɧɧɚɹ ɬɢɩɚ ɞɟɥɟɝɚɬɚ: Powers meth; // Ɂɧɚɱɟɧɢɟɦ ɩɟɪɟɦɟɧɧɨɣ ɬɢɩɚ ɞɟɥɟɝɚɬɚ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɚɧɨɧɢɦɧɵɣ ɦɟɬɨɞ: meth=delegate(double x){ double s=1; // ȼɵɱɢɫɥɟɧɢɟ ɩɪɨɢɡɜɟɞɟɧɢɹ: for(int i=1;i<=n;i++){ s*=x; } return s; // Ɋɟɡɭɥɶɬɚɬ ɚɧɨɧɢɦɧɨɝɨ ɦɟɬɨɞɚ }; return meth; // Ɋɟɡɭɥɶɬɚɬ ɫɬɚɬɢɱɟɫɤɨɝɨ ɦɟɬɨɞɚ } // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ // ɉɟɪɜɵɣ ɷɤɡɟɦɩɥɹɪ ɞɟɥɟɝɚɬɚ: Powers sqr=maker(2); // ȼɬɨɪɨɣ ɷɤɡɟɦɩɥɹɪ ɞɟɥɟɝɚɬɚ: Powers cube=maker(3); // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɨɜ ɞɟɥɟɝɚɬɚ: for(int i=1;i<=5;i++){ Делегаты и события Console.WriteLine( Ǝ{0,2}:{1,5}{2,5}{3,5}Ǝ, i,sqr(i),cube(i),maker(4)(i)); } При выполнении программы получаем следующий результат Результат выполнения программы (из листинга 2.10) 1: 1 1 1 2: 4 8 16 3: 9 27 81 4: 16 64 256 5: 25 125 Здесь мы объявляем делегат Powers для работы с методами, у которых один аргумент типа double и такого же типа результат. В главном методе мы описываем статический метод maker(). Он имеет целочисленный аргумент (обозначен как n), а в качестве результата возвращает ссылку на экземпляр делегата Powers. В теле метода объявляется локальная переменная meth типа Powers. Значением этой переменной присваивается такой анонимный метод x){ double s=1; for(int i=1;i<=n;i++){ s*=x; } return У метода один аргумент (обозначен как x) типа double, а результатом метод возвращает значение аргумента x в степени, определяемой параметром n (аргумент метода maker()). Для вычисления результата использован оператор цикла, в котором переменная s с начальным значением 1 умножается на x ровно n раз. Ссылка на экземпляр делегата, на который ссылается переменная meth, возвращается результатом Глава метода maker(). Таким образом, при вызове метода maker() с некоторым целочисленным аргументом n мы получаем ссылку на метод (точнее, ссылку на экземпляр делегата, который ссылается на метод. Этот метод при вызове с аргументом x возвращает результатом x в степени В главном методе командами Powers sqr=maker(2) и Powers cube=maker(3) объявляются две переменные sqr и cube типа Powers , и им присваиваются значения. Значением выражения вида sqr(i) является значение аргумента i в степени 2, а значение выражения это значение аргумента i в степени 3. Далее, значение выражения maker(4) — это ссылка на метод, который результатом возвращает значение своего аргумента в четвертой степени. Поэтому значение выражения вида maker(4)(i) есть нечто иное, как значение аргумента в степени 4. Таблица, отображаемая в результате выполнения программы, подтверждает наши выводы. Лямбда-выражения А, ничего опасного. Никакой контрреволюции. из к/ф Собачье сердце» У анонимных методов есть конкурент — это лямбда-выражения. Лямб- да-выражение представляет собой блок программного кода, который определяет метод и предназначен для присваивания в качестве значения переменной типа делегата. В этом смысле и лямбда-выражения, и анонимные методы служат одной цели. Разница лишь в том, как эта цель достигается. Итак, лямбда-выражение определяет метод. Как же это происходит Синтаксис описания лямбда-выражения идеологически близок копи- санию анонимного метода, нов несколько упрощенном виде. Условно лямбда-выражение можно рассматривать как состоящее из двух частей (левой и правой. Между ними размещается оператор =>. Левая часть определяет аргументы метода. В общем случае аргументы описываются в круглых скобках, для каждого аргумента указывается его тип и название. То есть фактически это круглые скобки с описанием аргументов метода. Отсутствие аргументов отображается пустыми круглыми скобками. В правой части, после оператора => описывается выделенный фигурными скобками блок, в котором размещаются команды, Делегаты и события 103 формирующие тело метода. Общий шаблон описания лямбда-метода такой (жирным шрифтом выделены основные элементы шаблона // Ɍɟɥɨ Строго говоря, если взять описание анонимного метода, убрать ключевое слово delegate и между блоками из круглых и квадратных скобок поставить оператор =>, то получим лямбда-выражение. Оно используется точно также, как и анонимный метод. Вместе стем при использовании лямбда-выражений можно использовать некоторые упрощения, которые часто делают описание соответствующего метода компактным. Ниже перечислены основные правила, которые позволяют упростить жизнь при работе с лямбда-выражениями: • Тип аргументов можно не указывать, если по контексту команды с лямб- да-выражением они могут быть определены автоматически (на основе переменной типа делегата, которой присваивается лямбда-выражение). • Если аргумент один и его тип не указывается, то круглые скобки можно не использовать Если в теле метода всего одна команда, то фигурные скобки можно не использовать Если в теле метода всего одна команда с инструкцией, то инструкцию можно не использовать. Например, ниже приведено лямбда-выражение, определяющее метод, который посимвольному и целочисленному аргументу вычисляет новый символ s,int n)=>{ int k=s+n; return (Мы могли бы определить такой же метод несколько иначе return (char)(s+n); } Глава Или еще проще: (s,n)=>(char)(s+n) А вот пример лямбда-выражения для метода, который для заданного значения аргумента возвращает квадрат аргумента (произведение аргумента на самого себя): n=>n*n То же самое, с помощью анонимного метода, мы могли бы описать следующим образом n){ return То есть экономия на программном коде очевидная. Далее мы рассмотрим несколько примеров, которые иллюстрируют, как вместо анонимных методов можно было бы использовать лямбда-выражения. В листинге 2.11 представлена версия программы из листинга 2.7, но вместо анонимных методов использованы лямбда-выражения (здесь и далее для сокращения объема кода несущественные комментарии удалены, а важные места выделены жирным шрифтом Листинг 2.11. Знакомство с лямбда-выражениями using System; delegate int Alpha(int n); delegate void Bravo(string t); class MyClass{ public int number; public MyClass(int n){ number=n; } public Alpha method; } Делегаты и события LambdaDemo{ static void Main(){ MyClass A=new MyClass(100); MyClass B=new MyClass(200); // ɉɨɥɸ ɨɛɴɟɤɬɚ ɡɧɚɱɟɧɢɟɦ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɟ: A.method=n=>A.number+n; // ɉɨɥɸ ɨɛɴɟɤɬɚ ɡɧɚɱɟɧɢɟɦ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɟ: B.method=(int n)=>{ return B.number-n; }; int x=80; Console.WriteLine( ƎA.method({0})={1}Ǝ,x,A.method(x)); A.number=300; Console.WriteLine( ƎA.method({0})={1}Ǝ,x,A.method(x)); Console.WriteLine( ƎB.method({0})={1}Ǝ,x,B.method(x)); Bravo show; // ɉɪɢɫɜɚɢɜɚɧɢɟ ɩɟɪɟɦɟɧɧɨɣ ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɹ: show=t=>Console.WriteLine(“ Ɍɟɤɫɬ: \”{0}\””,t); show( ƎBravoƎ); // ɉɪɢɫɜɚɢɜɚɧɢɟ ɩɟɪɟɦɟɧɧɨɣ ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɹ: show=(string t)=>{ for(int k=0;k Console.Write(“|”+t[k]); } Console.WriteLine(“|”); }; show( ƎBravoƎ); } } Глава Результат выполнения программы следующий Результат выполнения программы (из листинга 2.11) A.method(80)=180 A.method(80)=380 B.method(80)=120 Ɍɟɤɫɬ: В листинге 2.12 представлена новая версия программы из листинга 2.8. Листинг 2.12. Лямбда-выражение как аргумент System; delegate char MyDelegate(int n); class MyClass{ public char symbol; public MyDelegate get; public MyClass(char s,MyDelegate md){ symbol=s; get=md; } public void set(MyDelegate md){ get=md; } } class LambdaAsArgDemo{ static void Main(){ // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ. ȼɬɨɪɵɦ ɚɪɝɭɦɟɧɬɨɦ ɹɜɥɹɟɬɫɹ // ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɟ: MyClass obj=new MyClass( ƍKƍ,n=>(char)(ƍAƍ+n)); Console.WriteLine( Ǝɋɢɦɜɨɥ: \ƍ{0}\ƍƎ,obj.get(3)); // ȼɵɡɨɜ ɦɟɬɨɞɚ, ɚɪɝɭɦɟɧɬɨɦ ɤɨɬɨɪɨɦɭ ɩɟɪɟɞɚɧɨ // ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɟ: Делегаты и события obj.set(n=>(char)(obj.symbol+n)); Console.WriteLine( Ǝɋɢɦɜɨɥ: \ƍ{0}\ƍƎ,obj.get(3)); Ниже представлен результат выполнения программы Результат выполнения программы (из листинга 2.12) ɋɢɦɜɨɥ: 'D' ɋɢɦɜɨɥ: 'Программа в листинге 2.13 — это новая версия программы из листинга 2.9. Листинг 2.13. Лямбда-выражение как результат System; delegate int MyDelegate(); class LambdaAsResultDemo{ static MyDelegate calculate(int n){ int count=0; // Ɋɟɡɭɥɶɬɚɬ ɪɟɚɥɢɡɨɜɚɧ ɱɟɪɟɡ ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɟ: return ()=>{ count+=n; return count; }; } static void Main(){ MyDelegate next=calculate(1); for(int i=1;i<=5;i++){ Console.Write(next()+ Ǝ Ǝ); } Console.WriteLine(); next=calculate(3); for(int i=1;i<=5;i++){ Глава 2 108 Console.Write(next()+ Ǝ Ǝ); } Console.WriteLine(); Результат выполнения программы такой Результат выполнения программы (из листинга 2.13) 1 2 3 4 5 3 6 9 12 Наконец, в листинге 2.14 показано, как будет выглядеть программа из листинга 2.10, если вместо анонимных методов использовать лямб- да-выражения. Листинг 2.14. Реализация результата с помощью лямбда-выражения using System; delegate double Powers(double x); class MoreLambdaAsResultDemo{ static Powers maker(int n){ Powers meth; // Ɂɧɚɱɟɧɢɟɦ ɩɟɪɟɦɟɧɧɨɣ ɬɢɩɚ ɞɟɥɟɝɚɬɚ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɥɹɦɛɞɚ-ɜɵɪɚɠɟɧɢɟ: meth=x=>{ double s=1; for(int i=1;i<=n;i++){ s*=x; } return s; }; return meth; } static void Main(){ Делегаты и события Powers sqr=maker(2); Powers cube=maker(3); for(int i=1;i<=5;i++){ Console.WriteLine( Ǝ{0,2}:{1,5}{2,5}{3,5}Ǝ,i,sqr(i),cube(i),maker(4)(i)); } Ниже приведен результат выполнения программы Результат выполнения программы (из листинга 2.14) 1: 1 1 1 2: 4 8 16 3: 9 27 81 4: 16 64 256 5: 25 125 Во всех случаях, хотя программный код изменился, результат выполнения программ такой же, как и у их предшественниц. Несложно заметить, что для небольших (по объему кода) методов использование лямбда-выражений значительно упрощает структуру и читабельность программы. { i НАЗ А МЕТКУ Не все лямбда-выражения в примерах выше описаны оптимальным образом. Причина связана с желанием показать различные способы описания лямбда-выражений. Но различие между лямбда-выражениями и анонимными методами не сводится к чисто косметическим эффектам. Например, лямбда-выра- жения могут использоваться для описания индексаторов и свойств, которые имеют только get-аксессор. В таком случае вместо блока из фигурных скобок с аксессорами для свойства или индексатора указывается оператор => и значение, считываемое для свойства или индексатора. Небольшой пример поэтому поводу представлен в листинге 2.15. Глава 2 110 Листинг 2.15. Лямбда-выражения в свойствах и индексаторах using System; // Ʉɥɚɫɫ ɫɨ ɫɜɨɣɫɬɜɨɦ ɢ ɢɧɞɟɤɫɚɬɨɪɨɦ: class MyClass{ // Ɂɚɤɪɵɬɨɟ ɬɟɤɫɬɨɜɨɟ ɩɨɥɟ: private string text; // Ʉɨɧɫɬɪɭɤɬɨɪ ɫ ɬɟɤɫɬɨɜɵɦ ɚɪɝɭɦɟɧɬɨɦ: public MyClass(string t){ text=t; } // ɂɧɞɟɤɫɚɬɨɪ: public char this[int k]=>text[k]; // ɋɜɨɣɫɬɜɨ: public int length=>text.Length; } // Ʉɥɚɫɫ ɫ ɝɥɚɜɧɵɦ ɦɟɬɨɞɨɦ: class MoreLambdaDemo{ // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ: MyClass obj=new MyClass( ƎAlphaƎ); // ɂɫɩɨɥɶɡɨɜɚɧɢɟ ɢɧɞɟɤɫɚɬɨɪɚ ɢ ɫɜɨɣɫɬɜɚ: for(int k=0;k Ǝ|Ǝ+obj[k]); } Console.WriteLine( Ǝ|Ǝ); } } Делегаты и события 111 Результат выполнения программы следующий Результат выполнения программы (из листинга Программа простая описывается класс MyClass с закрытым текстовым полем text. Значение полю присваивается при создании объекта (значение передается аргументом конструктору. Индексатор описан так char this[int Эта запись означает, что при считывании значения проиндексированного объекта результатом возвращается символ из текстового поля с индексом, указанным в квадратных скобках. Похожим образом описано целочисленное свойство length: public int Значением свойства возвращается размер текста в текстовом поле. В главном методе проверяется функциональность созданного кода класса. В завершение раздела заметим, что лямбда-выражения считаются более предпочтительным механизмом по сравнению с анонимными методами. |