Приветствую всех, сегодня хотел затронуть тему о делегатах, и их применения.
Для работы с делегатами нам необходимо объявить тип делегата и тип указателей на метод оно и будет именем делегата.
1 |
delegate возвращаемый_тип имя (список_параметров); |
1 |
public delegate int num(int i); |
Который в нашем случаи принимает число в качестве аргумента и возвращает так же число. Делегаты имеют уровень класса и объявляются в пространстве имен. А их объявление должно начинаться с ключевого слова delegate. Делегаты могут быть использованы для вызова как статических методов, так и методов экземпляра. После того как создан делегат мы можем объявить поле:
1 |
public nameDelegat MyMethod; |
Однако применения поля в моем примере я не использовал, так как оно лишне, поля используют с ключевым словом event после public который объявляет событие в классе. Но об этом мы поговорим позже.
Поле делегата мы создали, теперь создадим метод на который будет указывать делегат:
1 2 3 4 |
static int metodSquare(int i) { return i * i; } |
Имя метода может быть любым, однако принимать и возвращать он должен один и тот же тип что и делегат. Стоит помнить тип может быть возвращаемым или нет void, по аналогии с методами, он в целом и похож на метод, за исключением того что он у него нет тела, но это только внешне, в работе он отличается от методов.
Рассмотрим полностью код программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//Объявляем делегат public delegate int nameDelegat(int i); //Основной класс программы public class Programm { //метод на который указывает делегат static int methodSquare(int i) { return i * i; } //Статический метод, возвращающий вычисления двух аргументов static int Calc(nameDelegat f, int n) { int s = 0; for (int i = 0; i <= n; i++) { s = s + f(i); } return s; } //Главный метод программы (точка входа) static void Main() { int result = Calc(methodSquare, 12); Console.WriteLine(result); Console.ReadKey(); } |
Рассмотрим код программы. Вызываем метод Calc и передаем в качестве параметра methodSquare и число 12. Если бы мы рассматривали обычный метод, то по определению должен был бы вызваться метод metodSquare, но с делегатами происходит вызов Calc. Внутри которого входим в цикл for и вызывается метод methodSquare, так как аргумент который мы приняли имел тип nameDelegat в переменной f это указатель делегата на метод. Произошел вызов methodSquare который произвел вычисления и вернул результат в метод Calс, в котором продолжился цикл for, по окончанию которого результат был передан в метод Main в консоль.
Делегаты предназначен для передачи метода в качестве аргументов других методов. Не много запутанно, но суть сводиться к тому что мы может передавать метод в качестве аргумента и это все благодаря делегату. И при вызове основного метода, будет вызван делегат который указывает на свой метод, после того как метод отработал он возвращается в основной метод. Для большего понимания тонкостей в работе делегата, лучше использовать отладку и пошагово посмотреть как происходят вызовы методов.
Следующий пример показывает как с помощью делегата, используя анонимные методы и лямбда-метод вычислить арифметическое среднее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public delegate int MyDelegate(); public delegate int MyDel(MyDelegate [] mass); class Program { public static int RandomNext() { Thread.Sleep(500); return new Random().Next(1, 100); } static void Main() { MyDelegate[] mass = new MyDelegate[2]; for (int i = 0; i < mass.Length; i++) { mass[i] = () => RandomNext(); } MyDel my; my = delegate (MyDelegate [] mas) { int s=0; for(int i=0; i < mas.Length; i++) { s += mass[i].Invoke(); } return s/mas.Length; }; Console.WriteLine(my(mass)); Console.ReadKey(); } } |
Как использовать дженерики внутри делегатов?
Приведенный пример ниже, покажет как использовать в делегатах дженерики:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
//Объявляем делегат public delegate int nameDelegat<T>(T i); //Основной класс программы public class Programm { //метод на который указывает делегат имеет две перегрузки static int metodSquare<T>(int i) { i *= i; return i ; } static int metodSquare<T>(double i) { i *= i; return (int)i; } //Статический метод, возвращающий вычисления static int Calc<T>(nameDelegat <T> f, T n) { var i = f(n); return i; } //Главный метод программы (точка входа) static void Main() { int result = Calc<int>(metodSquare<int>, 5); double result2 = Calc<double>(metodSquare<double>,2.5); Console.WriteLine(result); Console.WriteLine(result2); Console.ReadKey(); } |
Метод methodSquare получил перегрузку метода, он может принимать как тип int так и double. Однако я сделал метод Calc обобщающим (универсальным) он принимает любой тип данных, а делегат уже сам определяет какую из перегрузок ему выбрать.
Однако мы можем воспользоваться упрощенным вариантом использования делегата, без его объявления, а использовать его в параметре метода Calc встроенный, для этого посмотрим код ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//Основной класс программы public class Programm { //метод на который указывает делегат имеет две перегрузки static int metodSquare<T>(int i) { i *= i; return i ; } static int metodSquare<T>(double i) { i *= i; return (int)i; } //Статический метод, возвращающий вычисления static int calc<T>(Func <T,int> f, T n) { var i = f(n); return i; } //Главный метод программы (точка входа) static void Main() { int result = calc<int>(metodSquare<int>, 5); double result2 = calc<double>(metodSquare<double>,2.5); Console.WriteLine(result); Console.WriteLine(result2); Console.ReadKey(); } |
В приведенном примере выше мы в методе Calc воспользовались встроенным делегатом в Net который принимает T, а возвращает int.
Стоит запомнить что первые параметры Func всегда будут те что принимает делегат, а последним то что отдает.
Что такое анонимный делегат?
Это способ записи методов, а нужен он лишь для того что бы сократить функциональный код вашей программы. Посмотрим пример выше и используем анонимный делегат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//Основной класс программы public class Programm { //Статический метод, возвращающий вычисления static int calc<T>(Func<T, int> f, T n) { var i = f(n); return i; } //Главный метод программы (точка входа) static void Main() { int result = calc<int>(delegate(int i){ i *= i; return i;}, 5); double result2 = calc<double>(delegate(double i){i *= i;return (int)i;}, 2.5); Console.WriteLine(result); Console.WriteLine(result2); Console.ReadKey(); } } |
Что же мы видим, функционал метода methodSquare мы вынесли в строку вызова метода Calc, из метода Main, если воспользоваться отладчиком то мы увидим, что компилятор создал за нас метод methodSquare только назвал его иначе, и воспользовался нашим функционалом вычисления так как мы его передали сами.
Как использовать Лямда-выражение в делегатах?
Лямда -выражения это стиль записи, который позволяет еще больше сократить код программы. Мы это и сделаем из нашего примера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//Основной класс программы public class Programm { //Статический метод, возвращающий вычисления static int calc<T>(Func<T, int> f, T n) { var i = f(n); return i; } //Главный метод программы (точка входа) static void Main() { int result = calc<int>(delegate(int i){ i *= i; return i;}, 5); //анонимный делегат int resultI = calc<int>(i=>i*=i,5); //лямбда-выражение double result2 = calc<double>(delegate(double i){i *= i;return (int)i;}, 2.5); //анонимный делегат double resultD = calc<double>((i => (int)Math.Round(i *= i)), 2.5); //лямбда-выражение Console.WriteLine(result); Console.WriteLine(resultI); Console.WriteLine(result2); Console.WriteLine(resultD); Console.ReadKey(); } |
Как видите использование лямда-выражения уменьшило код почти в два раза в методе вызова делегата. Что касается кода использованного в лямда-выражении, мы видим что он берет i => и возвращает квадратный корень от i
В конце статьи хочу привести еще один пример основанный на предыдущих, но демонстрирует сумму двух чисел, в не зависимости от его типа данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Program { public static T Calc<T>(Func<T, T, T> d,T a, T b) { return d(a, b); } static void Main() { var resualt = Calc<int>((a, b) =>a=a+b, 6, 5); //11 var resualt2 = Calc<double>((a, b) => a = a + b, 5.5, 4.5); //10 Console.WriteLine(resualt); Console.WriteLine(resualt2); Console.ReadKey(); } } |
Еще хотело упомянуть об одной особенности лямбда-выражения в делегатах так называемом Замыкании.
Мы знаем что при использовании делегатов в лямбда-выражении, создается анонимный метод который принимает и производит вычислений, при использовании полей и локальных переменных, создаются ссылки на них и они используются как анонимные методы. Вы спросите о чем я? Сейчас все увидите на примере ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static void Main() { var functions = new List<Func<int, int>>(); for (int i = 0; i < 10; i++) functions.Add(x => x + i); foreach (var e in functions) Console.WriteLine(e(1)); Console.ReadKey(); } |
Как вы думаете что будет выведено в консоль? Подумали? Подумайте еще… Могу сказать что вы подумали о том что будет выведено 1,2,3,4…11 Однако это не так. А все дело в том что создалась ссылка на переменную i которая изменяла свое значение и меняло его во всем листе, в начале были все листы 1, потом 2 пока они все имели значения 11. А все потому что мы в лямбда-выражении передали переменную i из цикла. А как же использовать тогда этот вариант правильно, для этого надо завести локальную переменную в цикле, ее и передавать, рассмотрим пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private static void Main() { var functions = new List<Func<int, int>>(); for (int i = 0; i < 10; i++) { var j = i; functions.Add(x => x + j); } foreach (var e in functions) Console.WriteLine(e(1)); Console.ReadKey(); } |
Теперь результат будет тот который мы и ожидали первоначально 1,2,3,4,5