Васильев А.Н. Основы программирования на C#. Васильев А. Н. Программирование
Скачать 5.54 Mb.
|
i НАЗ А МЕТКУ Таким образом, выражения вида obj[k] мы можем отождествлять с именем метода. Еще один небольшой пример иллюстрирует особенности реализации ссылок на методы объектов. Рассмотрим программный код, представленный в листинге 2.6. Глава 2 84 Листинг 2.6. Ссылки на методы и ссылки на объекты System; // Ɉɛɴɹɜɥɟɧɢɟ ɞɟɥɟɝɚɬɚ: delegate void MyDelegate(int n); // Ʉɥɚɫɫ: class MyClass{ // Ɂɚɤɪɵɬɨɟ ɰɟɥɨɱɢɫɥɟɧɧɨɟ ɩɨɥɟ: private int number; // Ʉɨɧɫɬɪɭɤɬɨɪ ɫ ɨɞɧɢɦ ɚɪɝɭɦɟɧɬɨɦ: public MyClass(int n){ // ɉɪɢɫɜɚɢɜɚɧɢɟ ɡɧɚɱɟɧɢɹ ɩɨɥɸ: set(n); } // Ɇɟɬɨɞ ɞɥɹ ɩɪɢɫɜɚɢɜɚɧɢɹ ɡɧɚɱɟɧɢɹ ɩɨɥɸ: public void set(int n){ // ɉɪɢɫɜɚɢɜɚɧɢɟ ɡɧɚɱɟɧɢɹ ɩɨɥɸ: number=n; } // ɉɟɪɟɨɩɪɟɞɟɥɟɧɢɟ ɦɟɬɨɞɚ ToString(): public override string ToString(){ return Ǝɉɨɥɟ number=Ǝ+number; } } // Ʉɥɚɫɫ ɫ ɝɥɚɜɧɵɦ ɦɟɬɨɞɨɦ: class MethAndObjRefsDemo{ // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ: MyClass A=new MyClass(100); // Ɉɛɴɟɤɬɧɚɹ ɩɟɪɟɦɟɧɧɚɹ ɫɫɵɥɚɟɬɫɹ // ɧɚ ɪɚɧɟɟ ɫɨɡɞɚɧɧɵɣ ɨɛɴɟɤɬ: MyClass B=A; Делегаты и события // ɉɟɪɟɦɟɧɧɨɣ ɬɢɩɚ ɞɟɥɟɝɚɬɚ ɡɧɚɱɟɧɢɟɦ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɫɫɵɥɤɚ ɧɚ ɦɟɬɨɞ ɨɛɴɟɤɬɚ: MyDelegate apply=A.set; // ɉɟɪɟɦɟɧɧɨɣ ɩɪɢɫɜɚɢɜɚɟɬɫɹ ɫɫɵɥɤɚ ɧɚ ɧɨɜɵɣ ɨɛɴɟɤɬ: A=new MyClass(200); // ɉɪɨɜɟɪɤɚ ɡɧɚɱɟɧɢɣ ɩɨɥɟɣ ɨɛɴɟɤɬɨɜ: Console.WriteLine(A); Console.WriteLine(B); // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: apply(300); Console.WriteLine(); // ɉɪɨɜɟɪɤɚ ɡɧɚɱɟɧɢɣ ɩɨɥɟɣ ɨɛɴɟɤɬɨɜ: Console.WriteLine(A); Console.WriteLine(B); Ниже показано, как выглядит результат выполнения программы Результат выполнения программы (из листинга 2.6) ɉɨɥɟ number=200 ɉɨɥɟ number=100 ɉɨɥɟ number=200 ɉɨɥɟ В программе объявляется делегат MyDelegate, соответствующий методам, имеющим целочисленный аргумент и не возвращающим результат. Также мы используем класс MyClass. В классе описано закрытое целочисленное поле number. Значение полю присваивается при создании объекта. Не возвращающий результат метод set() получает при вызове целочисленный аргумент, значение которого присваивается полю number . Также в классе переопределен метод ToString(), возвращающий результатом текстовую строку со значением поля number объекта Глава В главном методе мыс помощью команды MyClass A=new MyClass(100) создаем объект, значение поля number которого равно 100. После выполнения команды MyClass B=A переменные A и B ссылаются на один и тот же объект. При выполнении команды MyDelegate apply=A.set создается экземпляр делегата MyDelegate, он содержит ссылку на метод set() объекта, а ссылка на экземпляр делегата записывается в переменную apply. После этого с помощью команды A=new MyClass(200) создается новый объект (значение поля number этого объекта равно 200) и ссылка на него записывается в переменную A. Получается такая ситуация при присваивании значения переменной apply переменная A ссылалась на один объекта теперь она ссылается на другой объект. Возникает естественный вопрос на метод set() какого объекта будет ссылаться (через экземпляр делегата) переменная apply? Ответ следующий переменная apply ссылается на метод того объекта, на который ссылалась переменная A на момент присваивания значения переменной apply. Другими словами, при выполнении команды apply=A.set создается ссылка на метод set() объекта, на который ссылается переменная A (напомним, что переменная B ссылается на тот же объект. И даже если затем переменная A получит новое значение и будет ссылаться на другой объект, переменная apply будет связана все стем же объектом. Для нее ничего не меняется. Проверить справедливость данного утверждения легко. У насесть две переменные переменная A ссылается на новый объект (значение поля 200), а переменная B ссылается на исходный объект (значение поля 100). Сначала проверяем значения полей объектов (команды Console.WriteLine(A) и Console.WriteLine(B)), а затем командой) вызываем экземпляр делегата, связанного с методом set() объекта, на который ссылается переменная B, и снова проверяем значения полей объектов. Как видим, изменилось поле того объекта, на который ссылается переменная Знакомство с анонимными методами Никто такого еще не видел. Понаблюдаем. из к/ф Собачье сердце» Выше мы имели дело с примерами, в которых переменным типа делегата в качестве значения присваивались ссылки на методы. Методы были статические или нестатические, нов любом случае они описывались Делегаты и события 87 заранее в соответствующем классе. Можно сказать, что эти методы имели самостоятельную ценность и теоретически мы могли бы их использовать так, как мы обычно использовали методы, не выполняя на них никакие ссылки с привлечением экземпляров делегата. С другой стороны, когда мы вызываем экземпляр делегата, то речь идет о вызове метода или методов — то есть о выполнении некоторой последовательности команд. Поэтому, в рамках концепции использования делегатов, логично было бы иметь возможность связывать экземпляры делегатов с некоторым блоком программного кода, который выполнялся бы при вызове экземпляра делегата и который бы ненужно было описывать в классе в виде метода. Такая возможность имеется. Реализуется она с помощью анонимных методов. Анонимный метод — это оформленный особым образом блок программного кода, который можно присвоить в качестве значения переменной типа делегата. В результате такой операции создается экземпляр делегата (на который будет ссылаться переменная. Этот экземпляр делегата ссылается на метод, который будет вызываться при вызове экземпляра делегата. Причем под методом здесь понимается набор команд, содержащийся в описании анонимного метода. Анонимный метод описывается практически также, как и обычный метод. Но понятно, что имеются некоторые особенности. А именно, описание анонимного метода начинается с ключевого слова delegate. Это ключевое слово фактически является заменителем имени метода у анонимного метода имени нет. Тип результата для анонимного метода не указывается. Аргументы описываются после ключевого слова delegate . Описываются они также, как и у обычного метода указывается тип аргумента и его название, объявления аргументов разделяются запятыми. Далее в фигурных скобках описывается тело анонимного метода — то есть те команды, которые выполняются при вызове метода. Общий шаблон описания анонимного метода следующий (жирным шрифтом выделены ключевые элементы шаблона // Ɍɟɥɨ Анонимный метод не может быть вызван сам по себе. Как отмечалось выше, анонимный метод (блок с описанием метода) присваивается в качестве значения переменной типа делегата. При этом анонимный метод может содержать ссылки на внешние (по отношению к методу) переменные Глава 2 88 q ПОДРОБНОСТИ bТип возвращаемого результата для анонимного метода не указывается. При этом анонимный метод может возвращать результат. И здесь нужно учесть, что анонимный метод существует не сам по себе, а присваивается в качестве значения переменной делегата. Но при описании соответствующего делегата был указан тип результата для метода, который может присваиваться в качестве значения экземпляру делегата. Получается, что тип результата анонимного метода неявно определяется описанием делегата (определяющего тип переменной, которой присваивается анонимный метод). В листинге 2.7 представлена программа, в которой используются анонимные методы Листинг 2.7. Знакомство с анонимными методами 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; } // Ʉɥɚɫɫ ɫ ɝɥɚɜɧɵɦ ɦɟɬɨɞɨɦ: class AnonymousMethDemo{ // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: Делегаты и события static void Main(){ // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɨɜ: MyClass A=new MyClass(100); MyClass B=new MyClass(200); // ɉɨɥɸ ɨɛɴɟɤɬɚ ɡɧɚɱɟɧɢɟɦ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɚɧɨɧɢɦɧɵɣ ɦɟɬɨɞ: A.method=delegate(int n){ return A.number+n; }; // ɉɨɥɸ ɨɛɴɟɤɬɚ ɡɧɚɱɟɧɢɟɦ ɩɪɢɫɜɚɢɜɚɟɬɫɹ // ɚɧɨɧɢɦɧɵɣ ɦɟɬɨɞ: B.method=delegate(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=delegate(string t){ Console.WriteLine( ƎɌɟɤɫɬ: \Ǝ{0}\ƎƎ,t); }; // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: show( ƎBravoƎ); // ɉɪɢɫɜɚɢɜɚɧɢɟ ɩɟɪɟɦɟɧɧɨɣ ɚɧɨɧɢɦɧɨɝɨ ɦɟɬɨɞɚ: Глава 2 90 show=delegate(string t){ for(int k=0;k Ǝ|Ǝ+t[k]); } Console.WriteLine( Ǝ|Ǝ); }; // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: show( ƎBravoƎ); Ниже представлен результат выполнения программы Результат выполнения программы (из листинга 2.7) A.method(80)=180 A.method(80)=380 B.method(80)=120 Ɍɟɤɫɬ: В программе мы объявляем два делегата делегат Alpha соответствует методам, имеющим целочисленный аргумент и возвращающим целочисленный результата делегат Bravo подразумевает использование методов, имеющих текстовый аргумент и не возвращающих результат. Класс MyClass имеет открытое целочисленное поле number, значение которому присваивается при вызове конструктора. Также в классе объявлено поле method типа Alpha. В это поле можно записать ссылку на экземпляр делегата, ссылающегося на метод, имеющий целочисленный аргумент и возвращающий целочисленный результат. Нона практике это означает, что значением полю можно присвоить ссылку на метод и, как следствие, такому полю можно присвоить в качестве значения анонимный метод НАЗ А МЕТКУ Напомним, что делегаты поддерживают многократную адресацию, поэтому экземпляры делегата могут быть связаны нес одним Делегаты и события 91 методом, а со списком методов, которые вызываются при вызове экземпляра делегата. В главном методе программы командами MyClass A=new MyClass(100) и MyClass B=new MyClass(200) создаются объекты A и B класса. У объекта A поле number равно 100, ау объекта B поле number равно Далее полю method каждого из объектов значением присваивается анонимный метод. Для объекта A команда присваивания следующая n){ return Полю method объекта A присваивается в качестве значения конструкция, которая начинается с ключевого слова delegate и заканчивается закрывающей фигурной скобкой, после которой стоит точка с запятой. То есть значением присваивается такое выражение n){ return Это и есть описание анонимного метода. У него один целочисленный аргумент (обозначен как n), а результатом метод возвращает значение выражения A.number+n (сумма значения поля number объекта и аргумента метода n). Именно такой код будет выполняться при вызове экземпляра делегата, на который ссылается поле method объекта Полю method объекта B также присваивается анонимный метод, ноне- много другой. Соответствующая команда выглядит следующим образом n){ return В данном случае анонимный метод определен таким образом, что результатом он возвращает разность значения поля number объекта B и аргумента метода n. Глава Работу анонимных методов проверяем, вызывая соответствующие экземпляры делегатов командами A.method(x)) и B.method(x) (при значении 80 для переменной x). Легко убедиться, что результат вычисляется в соответствии стем, как мы определили анонимные методы НАЗ А МЕТКУ Обратите внимание на одно обстоятельство. Объекты A и B созданы на основе класса MyClass , поэтому у каждого из объектов есть поле method . Поскольку поле является ссылкой на экземпляр делегата, мы можем его вызывать как метод. Создается иллюзия, что мы имеем дело с методом. Но этот методу разных объектов разный, хотя объекты относятся к одному классу. Фактически с помощью анонимных методов мы можем менять код, связанный с полем method , таким же образом, как мы меняем значение для числовых полей. Также в главном методе командой Bravo show объявляется переменная с типом Bravo (делегат. Значением такой переменной можно присвоить ссылку на метод, принимающий текстовый аргумент и не возвращающий результат. Сначала значение переменной show присваивается такой командой t){ Console.WriteLine( ƎɌɟɤɫɬ: Переменной значением присваивается анонимный метод, который отображает в консольном окне сообщение, содержащее в двойных кавычках текст, переданный аргументом методу. После выполнения такого присваивания командой show( ƎBravoƎ) через переменную show вызываем экземпляр делегата и проверяем работу анонимного метода. Затем переменной show присваивается в качестве значения новый анонимный метод, как показано ниже t){ for(int k=0;k Ǝ|Ǝ+t[k]); } Console.WriteLine( Ǝ|Ǝ); }; Делегаты и события 93 На этот раз текстовый аргумент метода отображается посимвольно в консольном окне, с использованием вертикальной черты в качестве разделителя символов. С помощью команды show( ƎBravoƎ) убеждаемся в том, что все происходит так, как описано в анонимном методе НАЗ А МЕТКУ Присваивая переменной show в качестве значения различные анонимные методы, мы создаем впечатление, что имеем дело с методом, программный код которого меняется по мере выполнения про- граммы. Использование анонимных методов Что-то вы меня больно утесняете, папаша. из к/ф Собачье сердце» Мы рассмотрим несколько примеров, в которых используются анонимные методы. Примеры призваны послужить иллюстрацией к тому, где, когда и как анонимные методы могут оказаться полезными. Выше упоминалось, что анонимные методы предназначены для присваивания в качестве значений переменным типа делегата. Но это вовсе не означает, что мы не можем передать анонимный метод аргументом другому методу или даже конструктору (правда тут имеются некоторые особенности. Противоречия здесь нет, поскольку соответствующий аргумент описывается как переменная типа делегата, и когда фактическим аргументом передается анонимный метод, то технически переменной типа делегата значением присваивается анонимный метод. То есть все формальности будут выдержаны. Сначала рассмотрим очень простой пример, в котором имеется делегат MyDelegate , соответствующий методам с одним целочисленным аргументом и символьным результатом. В программе описывается классу которого есть открытое символьное поле symbol, конструктор с двумя аргументами и метод set(). Также в классе описано поле get , которое является ссылкой на экземпляр делегата MyDelegate. Первым аргументом конструктору передается символьное значение, присваиваемое полю symbol. Второй аргумент конструктора является Глава ссылкой на экземпляр делегата MyDelegate. Эта ссылка записывается в поле get. Программный код представлен в листинге 2.8. Листинг 2.8. Анонимный метод как аргумент 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 AnonymMethAsArgDemo{ // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ Делегаты и события // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ. ȼɬɨɪɵɦ ɚɪɝɭɦɟɧɬɨɦ ɹɜɥɹɟɬɫɹ // ɚɧɨɧɢɦɧɵɣ ɦɟɬɨɞ: MyClass obj=new MyClass('K', // ɋɢɦɜɨɥɶɧɵɣ ɚɪɝɭɦɟɧɬ delegate(int n){ // Ⱥɧɨɧɢɦɧɵɣ ɦɟɬɨɞ return (char)( ƍAƍ+n); } ); // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: Console.WriteLine( Ǝɋɢɦɜɨɥ: \ƍ{0}\ƍƎ,obj.get(3)); // ȼɵɡɨɜ ɦɟɬɨɞɚ, ɚɪɝɭɦɟɧɬɨɦ ɤɨɬɨɪɨɦɭ ɩɟɪɟɞɚɧ // ɚɧɨɧɢɦɧɵɣ ɦɟɬɨɞ: obj.set( delegate(int n){ return (char)(obj.symbol+n); } ); // ȼɵɡɨɜ ɷɤɡɟɦɩɥɹɪɚ ɞɟɥɟɝɚɬɚ: Console.WriteLine( Ǝɋɢɦɜɨɥ: \ƍ{0}\ƍƎ,obj.get(3)); Ниже показан результат выполнения программы Результат выполнения программы (из листинга 2.8) ɋɢɦɜɨɥ: 'D' ɋɢɦɜɨɥ: 'Для нас первоочередной интерес представляет код главного метода программы. В нем создается объект obj класса MyClass. Причем второй аргумент конструктора описан довольно громоздким выражением, которое является описанием анонимного метода n){ return (char)( ƍAƍ+n); } Глава Вся эта конструкция является вторым аргументом конструктора. Это анонимный метод, который при вызове с целочисленным аргументом n результатом возвращает символ, смещенный по отношению к символу на n позиций. Ссылка на этот анонимный метод записывается через посредника (экземпляр делегата) в поле get. Поэтому при вызове данного экземпляра делегата командой obj.get(3) в качестве результата возвращается символ, отстоящий в кодовой таблице на 3 позиции в направлении увеличения кода) по отношению к символу ƍAƍ. { i НАЗ А МЕТКУ То есть в данном случае поле symbol объекта при определении анонимного метода не используется. Это, в общем-то, неслучайно. При создании объекта obj , когда вызывается конструктор, ссылка obj еще не может использоваться. Поэтому в теле анонимного метода ссылок на объект obj нет. Далее из объекта obj вызывается метод set(), которому в качестве аргумента передается следующая конструкция n){ return (Это анонимный метод, который присваивается в качестве значения полю get объекта obj. Результатом метод возвращает символ, смещенный на n (аргумент метода) позиций по отношению к символьному значению поля symbol объекта obj. При создании объекта obj поле symbol получило значение ƍKƍ. Поэтому значением выражения obj.get(3) будет символ, смещенный на 3 позиции по отношению к символу ƍKƍ. q |