
- •Лекция: Делегаты. Определение и обработка событий Функциональный тип в c#. Делегаты
- •Делегаты
- •Ковариантность и контравариантность
- •Классы с событиями
- •Класс sender. Как объявляются события?
- •Делегаты и события
- •Как зажигаются события
- •Классы receiver. Как обрабатываются события
- •Классы с событиями, допустимые в каркасе .Net Framework
- •Пример "Списки с событиями"
- •Класс sender
- •Классы receiver
- •Две проблемы с обработчиками событий
- •Игнорирование коллег
- •Переопределение значений аргументов события
Лекция: Делегаты. Определение и обработка событий Функциональный тип в c#. Делегаты
Делегаты в C# позволяют применять методы одного класса к объектам другого класса, который может эти методы вызвать. Вы можете применить метод m класса A, определенного в делегате, к классу B, который будет способен вызвать метод m класса A. Вы можете вызвать как статические методы, так и нестатические. Эта концепция хорошо известна разработчикам C++, которые использовали указатель на функцию в качестве параметра, передаваемого другой функции. Концепция делегатов была введена в Visual J++, а затем перенесена в C#. Делегаты C# являются в .NET Framework реализацией в качестве производного класса от System.Delegate. Использование делегатов можно описать четырьмя шагами.
Определение объекта делегата с сигнатурой, точно соответствующей сигнатуре метода, который пытаемся связать.
Определение метода с сигнатурой делегата, определенного на первом шаге
Создание объекта делегата и связывание их с методами
Вызов связанного метода с помощью объекта делегата
Слово делегат (delegate) используется в C# для обозначения хорошо известного понятия. Делегат задает определение функционального типа (класса) данных. Экземплярами класса являются функции. Описание делегата в языке C# представляет собой описание еще одного частного случая класса. Каждый делегат описывает множество функций с заданной сигнатурой. Каждая функция (метод), сигнатура которого совпадает с сигнатурой делегата, может рассматриваться как экземпляр класса, заданного делегатом. Синтаксис объявления делегата имеет следующий вид:
[<спецификатор доступа>] delegate <тип результата > <имя класса> (<список аргументов>);
Этим объявлением класса задается функциональный тип - множество функций с заданной сигнатурой, у которых аргументы определяются списком, заданным в объявлении делегата, и тип возвращаемого значения определяется типом результата делегата.
Спецификатор доступа может быть, как обычно, опущен. Где следует размещать объявление делегата? Как и у всякого класса, есть две возможности:
• непосредственно в пространстве имен, наряду с объявлениями других классов, структур, интерфейсов;
• внутри другого класса, наряду с объявлениями методов и свойств. Такое объявление рассматривается как объявление вложенного класса.
Так же, как и интерфейсы C#, делегаты не задают реализации. Фактически между некоторыми классами и делегатом заключается контракт на реализацию делегата. Классы, согласные с контрактом, должны объявить у себя статические или динамические функции, сигнатура которых совпадает с сигнатурой делегата. Если контракт выполняется, то можно создать экземпляры делегата, присвоив им в качестве значений функции, удовлетворяющие контракту. Заметьте, контракт является жестким: не допускается ситуация, при которой у делегата тип параметра - object, а у экземпляра соответствующий параметр имеет тип, согласованный сobject, например,int.
Начнем примеры этой лекции с объявления трех делегатов. Поместив два из них в пространство имен, третий вложим непосредственно в создаваемый нами класс:
namespace Delegates
{
//объявление классов - делегатов
delegate void Proc(ref int x);
delegate void 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.
Заметьте, экземпляры делегатов можно рассматривать как ссылки (указатели на функции), а методы тех или иных классов с соответствующей сигнатурой можно рассматривать как объекты, хранимые в динамической памяти. В определенный момент происходит связывание ссылки и объекта (в этой роли выступают не обычные объекты, имеющие поля, а методы, задающие код). Взгляд на делегата как на указатель функции характерен для программистов, привыкших к С++.
Приведу теперь процедуру, тестирующую работу созданного класса:
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, связанных к моменту вызова с закрытыми методами класса, приводит к выполнению нужных функций.
Еще пример. Следующий C# код показывает вышеупомянутые шаги в реализации одного делегата и четырех классов.
using System;
// Шаг 1. Определение делегата с сигнатурой связываемого метода
public delegate void MyDelegate(string input);
// Шаг 2. Определение метода с сигнатурой определенного делегата
class MyClass1{
public void delegateMethod1(string input){
Console.WriteLine(
"This is delegateMethod1 and the input to the method is {0}",
input);
}
public void delegateMethod2(string input){
Console.WriteLine(
"This is delegateMethod2 and the input to the method is {0}",
input);
}
}
// Шаг 3. Создание объектов делегата и связывание с методами
class MyClass2{
public MyDelegate createDelegate(){
MyClass1 c2=new MyClass1();
MyDelegate d1 = new MyDelegate(c2.delegateMethod1);
MyDelegate d2 = new MyDelegate(c2.delegateMethod2);
MyDelegate d3 = d1 + d2;
return d3;
}
}
// Шаг 4. Вызов метода с помощью делегата
class MyClass3{
public void callDelegate(MyDelegate d,string input){
d(input);
}
}
class Driver{
static void Main(string[] args){
MyClass2 c2 = new MyClass2();
MyDelegate d = c2.createDelegate();
MyClass3 c3 = new MyClass3();
c3.callDelegate(d,"Calling the delegate");
}
}
Еще пример: