Здравствуйте! Записался на ваш курс, но не понимаю как произвести оплату. Надо ли писать заявление и, если да, то куда отправлять? как я получу диплом о профессиональной переподготовке? |
Делегаты. Функциональный тип данных
Проект к данной лекции Вы можете скачать здесь.
Как определяется функциональный тип и как появляются его экземпляры
Слово делегат ( delegate ) используется в C# для обозначения хорошо известного понятия, давно употребляемого в языках программирования. Делегат C# задает определение функционального типа (класса) данных. Экземплярами такого класса являются функции. Делегаты языка C# наряду со структурами, перечислениями и интерфейсами представляют задание еще одного частного случая классов. Каждый делегат описывает множество функций с заданной сигнатурой. Каждая функция, или если быть более точным и придерживаться терминологии, принятой в объектно-ориентированном программировании, каждый метод, сигнатура которого совпадает с сигнатурой делегата, может рассматриваться как экземпляр класса, заданного делегатом. Синтаксис объявления делегата имеет следующий вид:
[<спецификатор доступа>] delegate <тип результата > <имя класса> (<список аргументов>);
Этим объявлением класса задается функциональный тип - множество функций с заданной сигнатурой, у которых аргументы определяются списком, заданным в объявлении делегата, и тип возвращаемого значения определяется типом результата делегата.
Спецификатор доступа может быть, как обычно, опущен. Где следует размещать объявление делегата? Как и у всякого класса, есть две возможности:
- непосредственно в пространстве имен, наряду с объявлениями других классов, структур, интерфейсов;
- внутри другого класса, наряду с объявлениями методов и свойств. Такое объявление рассматривается как объявление вложенного класса.
Так же, как и интерфейсы C#, делегаты не задают реализации. Фактически между некоторыми классами и делегатом заключается контракт на реализацию делегата. Классы, согласные с контрактом, могут объявить у себя статические или динамические функции, сигнатура которых совпадает с сигнатурой делегата. Далее они могут создать экземпляр делегата, присвоив ему в качестве значения функцию, удовлетворяющую контракт. Заметьте, контракт является жестким: не допускается ситуация, при которой у делегата тип параметра - object, а у функции, связываемой с экземпляром, соответствующий параметр имеет тип int, хотя и согласованный с типом object, но не совпадающий с ним.
Созданный экземпляр делегата - объект функционального типа можно использовать по-разному, например, в качестве фактического аргумента при вызове метода, формальный аргумент которого имеет тип, заданный делегатом.
Содержательный пример работы с делегатами приводился в проекте первой лекции. Читателю есть смысл вернуться к его рассмотрению. Напомню основные детали проекта, связанные с делегатами. Задача состояла в оценке времени работы группы методов, входящих в состав создаваемого класса MyMath, - sin(x), cos(x), tg(x) и им подобные методы, имеющие одинаковую сигнатуру. Для оценки времени был создан специальный класс TimeValue. В этом классе был описан вложенный класс-делегат:
/// <summary> /// Класс спроектирован для получения оценок времени /// выполнения различных методов. /// Встроенные делегаты определяют сигнатуры этих методов /// </summary> public class TimeValue { public delegate double DToD(double arg1); }
В класс Timevalue включен метод EvalTimeDToD, один из аргументов которого принадлежит функциональному типу, заданному делегатом:
/// <summary> /// Возвращает время в секундах, /// затраченное на вычисление count раз /// метода fun с сигнатурой, удовлетворяющей /// делегату DToD (double to double) /// </summary> /// <param name="count">число повторений</param> /// <param name="fun">имя функции</param> /// <param name="x">аргумент</param> /// <returns>время в милисекундах или тиках</returns> public static double EvalTimeDToD(int count, DToD fun, double x) { DateTime start, finish; double res = 0; start = DateTime.Now; for (int i = 1; i < count; i++) fun(x); finish = DateTime.Now; res = (finish - start).ticks; return res; }
Используя этот метод, клиенты класса TimeValue без труда могут оценить время работы любого метода, сигнатура которого задается делегатом DToD. Возможности класса легко расширить, определив новые делегаты.
Рассмотрим еще несколько более формальных примеров и начнем с объявления трех делегатов. Поместив два из них в пространство имен, третий вложим непосредственно в создаваемый нами класс QwnDel:
namespace Delegates { //объявление классов-делегатов delegate void Proc(ref int x); delegate string MesToPers(string s); class OwnDel { public delegate int Fun1(int x); int Plus1( int x){return(x+100);}//Plus1 int Minus1(int x){return(x-100);}//Minus1 void Plus(ref int x){x+= 100;} void Minus(ref int x){x-=100;} //поля класса public Proc p1; public Fun1 f1; char sign; //конструктор public OwnDel(char sign) { this.sign = sign; if (sign == '+') {p1 = new Proc(Plus);f1 = new Fun1(Plus1);} else {p1 = new Proc(Minus);f1 = new Fun1(Minus1);} } }//class OwnDel }
Прокомментирую этот текст.
- Первым делом объявлены три функциональных класса - три делегата: Proc, MesToPers, Fun1. Каждый из них описывает множество функций фиксированной сигнатуры.
- В классе OwnDel описаны четыре метода: Plus, Minus, Plus1, Minus1, сигнатуры которых соответствуют сигнатурам, задаваемым классами Proc и Fun1.
- Поля p1 и f1 класса OwnDel являются экземплярами классов Proc и Fun1.
- В конструкторе класса поля p1 и f1 связываются с конкретными методами Plus или Minus, Plus1 или Minus1. Связывание с той или иной функцией в данном случае определяется значением поля sign.
Заметьте, поскольку делегаты относятся к ссылочным типам и, соответственно, присваивание является ссылочным присваиванием, то экземпляры делегатов представляют собой ссылки (указатели на функции), а методы тех или иных классов с соответствующей сигнатурой можно рассматривать как объекты, хранимые в динамической памяти. В определенный момент происходит связывание ссылки и объекта (в этой роли выступают не обычные объекты, имеющие поля, а методы, задающие код). Взгляд на делегата как на указатель функции характерен для программистов, привыкших к С++.
Приведу теперь метод класса Testing - клиента класса OwnDel, тестирующую работу сервисов этого класса:
public void TestOwnDel() { int account = 1000, account1=0; OwnDel oda = new OwnDel('+'); Console.WriteLine("account = {0}, account1 = {1}", account, account1); oda.p1(ref account); account1=oda.f1(account); Console.WriteLine("account = {0}, account1 = {1}", account, account1); }
Клиент класса OwnDel создает экземпляр класса, передавая конструктору знак той операции, которую он хотел бы выполнить над своими счетами - account и account1. Вызов p1 и f1, связанных к моменту вызова с закрытыми методами класса, приводит к выполнению нужных функций.
В нашем примере объявление экземпляров делегатов и связывание их с внутренними методами класса происходило в классе поставщика. Клиенту оставалось лишь вызывать уже созданные экземпляры, но эту работу можно выполнять и на стороне клиентского класса, чем мы сейчас и займемся. Рассмотрим объявление многократно встречавшегося класса Person:
/// <summary> /// Класс Person с полями name, id, salary, message. /// Позволяет сравнивать объекты Person /// по разным критериям. /// </summary> class Person { //свойства string name; int id; double salary; string message; //конструкторы public Person() {name =""; id=0; salary=0.0;} public Person(string name) {this.name = name;} public Person (string name, int id, double salary) { this.name = name; this.id=id; this.salary = salary; } public Person (Person pers) { this.name = pers.name; this.id = pers.id; this.salary = pers.salary;} //доступ к свойствам public string Name { get { return (name); } set { name = value; } } public double Salary { get { return (salary); } set { salary = value; } } public int Id { get { return (id); } set { id = value; } } //методы /// <summary> /// Передает сообщение объекту /// </summary> /// <param name="mes">сообщение</param> /// <returns>имя с присоединенным сообщением</returns> public string ToPerson(string mes) { this.message = mes; return string.Format("{0}, {1}",name, message); } }//class Person
Класс Person будет использоваться и в других примерах данной лекции. При анализе текста этого класса прошу обратить внимание на метод класса ToPerson, сигнатура которого совпадает с сигнатурой, определенной делегатом MesToPers. Посмотрите, как клиент класса может связать этот метод с экземпляром делегата, определенного самим клиентом:
Person man1 = new Person("Владимир"); MesToPers mestopers = new MesToPers(man1.ToPerson); Console.WriteLine(mestopers("пора работать!"));
Обратите внимание, что поскольку метод ToPerson не является статическим методом, при связывании необходимо передать и объект, вызывающий метод. Более того, переданный объект становится доступным экземпляру делегата. Отсюда сразу же становится ясным, что экземпляры делегата - это не просто указатели на функцию, а более сложно организованные структуры. Они содержат ссылки как на метод, так и на объект, вызвавший метод. Вызываемый метод в своей работе использует как информацию, передаваемую ему через аргументы метода, так и информацию, хранящуюся в полях объекта.
В данном примере переданное сообщение "пора работать" присоединится к имени объекта, и результирующая строка будет выдана на печать. В тех случаях, когда метод, связываемый с экземпляром делегата, не использует информацию объекта, этот метод может быть объявлен как статический метод класса. Таким образом, инициализировать экземпляры делегата можно как статическими, так и динамическими методами, связанными с конкретными объектами.
Последние три строки были добавлены в вышеприведенную тестирующую процедуру TestOwnDel. Взгляните на результаты ее работы.