Занятие №66
Делегаты: понятие, правила описания и использования .
Делегаты - это объекты, которые указывают на методы. То есть делегаты - это указатели на методы и с помощью можно вызвать данные методы.
Определение делегатов
Для объявления делегата используется ключевое слово delegate , после которого идет возвращаемый тип, название и параметры. Например:
delegate void Message();
Делегат Message в качестве возвращаемого типа имеет тип void (то есть ничего не возвращает) и не принимает никаких параметров. Это значит, что этот делегат может указывать на любой метод, который не принимает никаких параметров и ничего не возвращает.
Пример1 применения этого делегата:
class Program
{
delegate void Message(); // Объявляем делегат
static void Main(string[] args)
{
Message mes; // Создаем переменную делегата
if (DateTime.Now.Hour
{
mes = GoodMorning; // Присваиваем этой
//переменной адрес метода
}
else
{
mes = GoodEvening;
}
mes(); // Вызываем метод
Console.ReadKey();
}
private static void GoodMorning()
{
Console.WriteLine("Good Morning");
}
private static void GoodEvening()
{
Console.WriteLine("Good Evening");
}
}
Сначала определяем делегата:
delegate void Message(); // Объявляем делегат
В данном случае делегат определяется внутри класса, но также можно определить делегат вне класса внутри пространства имен.
Для использования делегата объявляется переменная этого делегата:
Message mes; // Создаем переменную делегата
С помощью свойства DateTime.Now.Hour получаем текущий час. И в зависимости от времени в делегат передается адрес определенного метода. Методы имеют то же возвращаемое значение и тот же набор параметров (в данном случае отсутствие параметров), что и делегат.
//Присваиваем этой переменной адрес метода
mes = GoodMorning;
Затем через делегата вызывается метод, на который
ссылается данный делегат:
mes(); // Вызываем метод
Вызов делегата производится подобно вызову метода.
Пример2
class Program
{
delegate int Operation(int x, int y);
static void Main(string[] args)
{
// присваивание адреса метода через контруктор
Operation del = Add; // делегат указывает на метод Add
int result = del(4,5); // фактически Add(4, 5)
Console.WriteLine(result);
del = Multiply; // теперь делегат указывает на метод
// Multiply
result = del(4, 5); // фактически Multiply(4, 5)
Console.WriteLine(result);
Console.Read();
}
private static int Add(int x, int y)
{
return x+y;
}
private static int Multiply (int x, int y)
{
return x * y;
}
}
В данном случае делегат Operation возвращает значение типа int и имеет два параметра типа int. Поэтому этому делегату соответствует любой метод, который возвращает значение типа int и принимает два параметра типа int. В данном случае этометоды Add и Multiply.
То есть можно присвоить переменной делегата любой из этих методов и вызвать.
Так как делегат принимает два параметра типа int, то при его вызове необходимо передать значения для этих параметров: del(4,5).
Делегаты необязательно могут указывать только на методы, которые определены в том же классе, где определена переменная делегата. Это могут быть также методы из других классов и структур.
class Math
{
public int Sum(int x, int y) { return x + y; }
}
class Program
{
delegate int Operation(int x, int y);
static void Main(string[] args)
{
Math math = new Math();
Operation del = math.Sum;
int result = del(4, 5); // math.Sum(4, 5)
Console.WriteLine(result); // 9
Console.Read();
}
}
Присвоение ссылки на метод
Выше переменной делегата напрямую присваивался метод. Есть еще один способ - создание объекта делегата с помощью конструктора, в который передается нужный метод:
class Program
{
delegate int Operation(int x, int y);
static void Main(string[] args)
{
Operation del = Add;
Operation del2 = new Operation(Add);
Console.Read();
}
private static int Add(int x, int y) { return x + y; }
}
Оба способа равноценны.
Соответствие методов делегату
Как было написано выше, методы соответствуют делегату, если они имеют один и тот же возвращаемый тип и один и тот же набор параметров. Но надо учитывать, что во внимание также принимаются модификаторы ref и out . Например, пусть у нас есть делегат:
delegate void SomeDel(int a, double b);
Этому делегату соответствует, например, следующий метод:
void SomeMethod1(int g, double n) { }
А следующие методы НЕ соответствуют:
int SomeMethod2(int g, double n) { }
void SomeMethod3(double n, int g) { }
void SomeMethod4(ref int g, double n) { }
void SomeMethod5(out int g, double n) { g = 6; }
Здесь метод SomeMethod2 имеет другой возвращаемый тип, отличный от типа делегата. SomeMethod3 имеет другой набор параметров. Параметры SomeMethod4 и SomeMethod5 также отличаются от параметров делегата, поскольку имеют модификаторы ref и out.
Добавление методов в делегат
В примерах выше переменная делегата указывала на один метод. В реальности же делегат может указывать на множество методов, которые имеют ту же сигнатуру и возвращаемые тип. Все методы в делегате попадают в специальный список - список вызова или invocation list . При вызове делегата все методы из этого списка последовательно вызываются. И можно добавлять в этот список не один, а несколько методов:
class Program
{
delegate void Message();
static void Main(string[] args)
{
Message mes1 = Hello;
mes1 += HowAreYou; // теперь mes1 указывает на два метода
mes1(); // вызываются оба метода - Hello и HowAreYou
Console.Read();
}
private static void Hello()
{
Console.WriteLine("Hello");
}
private static void HowAreYou()
{
Console.WriteLine("How are you?");
}
}
В примере в список вызова делегата mes1 добавляются два метода - Hello и HowAreYou.
При вызове mes1 вызываются сразу оба этих метода.
Для добавления делегатов применяется операция += .
При добавлении делегатов можно добавить ссылку на один и тот же метод несколько раз, и в списке вызова делегата будет несколько ссылок на один и то же метод. При вызове делегата добавленный метод будет вызываться столько раз, сколько он был добавлен:
Message mes1 = Hello;
mes1 += HowAreYou;
mes1 += Hello;
mes1 += Hello;
mes1();
Результат:
Hello
How are you?
Hello
Hello
Можно удалять методы из делегата с помощью операции -= :
static void Main(string[] args)
{
Message mes1 = Hello;
mes1 += HowAreYou;
Message mes2 = HowAreYou;
Message mes3 = mes1 + mes2;
mes1(); // вызываются все методы из mes1
mes1 -= HowAreYou; // удаляем метод HowAreYou
mes1(); // вызывается метод Hello
Console.Read();
}
При удалении, если делегат содержит несколько ссылок на один и тот же метод, то операция -= начинает поиск с конца списка вызова делегата и удаляет только первое найденное вхождение.
Объединение делегатов
Делегаты можно объединять в другие делегаты.
Например:
class Program
{
delegate void Message();
static void Main(string[] args)
{
Message mes1 = Hello;
Message mes2 = HowAreYou;
Message mes3 = mes1 + mes2; // объединяем делегаты
mes3(); // вызываются все методы из mes1 и mes2
Console.Read();
}
private static void Hello()
{
Console.WriteLine("Hello");
}
private static void HowAreYou()
{
Console.WriteLine("How are you?");
}
}
В примере объект mes3 представляет объединение делегатов mes1 и mes2. Объединение делегатов значит, что в список вызова делегата mes3 попадут все методы из делегатов mes1 и mes2. И при вызове делегата mes3 все эти методы одновременно будут вызваны.
Вызов делегата
В примерах выше делегат вызывался как обычный метод. Если делегат принимал параметры, то при его вызове для параметров передавались необходимые значения:
class Program
{
delegate int Operation(int x, int y);
delegate void Message();
static void Main(string[] args)
{
Message mes = Hello;
mes();
Operation op = Add;
op(3, 4);
Console.Read();
}
private static void Hello() { Console.WriteLine("Hello"); }
private static int Add(int x, int y) { return x + y; }
}
Другой способ вызова делегата представляет метод Invoke() :
class Program
{
delegate int Operation(int x, int y);
delegate void Message();
static void Main(string[] args)
{
Message mes = Hello;
mes.Invoke();
Operation op = Add;
op.Invoke(3, 4);
Console.Read();
}
private static void Hello() { Console.WriteLine("Hello"); }
private static int Add(int x, int y) { return x + y; }
}
Если делегат пуст, то есть в его списке вызова нет ссылок ни на один из методов (то есть делегат равен Null), то при вызове такого делегата получаем исключительную ситуацию( сообщение об ошибке).
Поэтому при вызове делегата всегда лучше проверять, не равен ли он null.
Если делегат возвращает некоторое значение, то возвращается значение последнего метода из списка вызова (если в списке вызова несколько методов). Например:
class Program
{
delegate int Operation(int x, int y);
static void Main(string[] args)
{
Operation op = Subtract;
op += Multiply;
op += Add;
Console.WriteLine(op(7, 2)); // Add(7,2) = 9
Console.Read();
}
private static int Add(int x, int y) { return x + y; }
private static int Subtract(int x, int y) { return x - y; }
private static int Multiply(int x, int y) { return x * y; }
}
Делегаты как параметры методов
Делегаты могут быть параметрами методов:
class Program
{
delegate void GetMessage();
static void Main(string[] args)
{
if (DateTime.Now.Hour
{
Show_Message(GoodMorning);
}
else
{
Show_Message(GoodEvening);
}
Console.ReadLine();
}
private static void Show_Message(GetMessage _del)
{
_del?.Invoke();
}
private static void GoodMorning()
{
Console.WriteLine("Good Morning");
}
private static void GoodEvening()
{
Console.WriteLine("Good Evening");
}
}
Обобщенные делегаты
Делегаты могут быть обобщенными, например:
delegate T Operation(K val);
class Program
{
static void Main(string[] args)
{
Operation op = Square;
Console.WriteLine(op(5));
Console.Read();
}
static decimal Square(int n)
{
return n * n;
}
}