Приветствую всех, сегодня хотел затронуть тему о делегатах, и их применения.
Для работы с делегатами нам необходимо объявить тип делегата и тип указателей на метод оно и будет именем делегата.
delegate возвращаемый_тип имя (список_параметров);
public delegate int num(int i);
Который в нашем случаи принимает число в качестве аргумента и возвращает так же число. Делегаты имеют уровень класса и объявляются в пространстве имен. А их объявление должно начинаться с ключевого слова delegate. Делегаты могут быть использованы для вызова как статических методов, так и методов экземпляра. После того как создан делегат мы можем объявить поле:
public nameDelegat MyMethod;
Однако применения поля в моем примере я не использовал, так как оно лишне, поля используют с ключевым словом event после public который объявляет событие в классе. Но об этом мы поговорим позже.
Поле делегата мы создали, теперь создадим метод на который будет указывать делегат:
static int metodSquare(int i)
{
return i * i;
}
Имя метода может быть любым, однако принимать и возвращать он должен один и тот же тип что и делегат. Стоит помнить тип может быть возвращаемым или нет void, по аналогии с методами, он в целом и похож на метод, за исключением того что он у него нет тела, но это только внешне, в работе он отличается от методов.
Рассмотрим полностью код программы:
//Объявляем делегат
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 в консоль.
Делегаты предназначен для передачи метода в качестве аргументов других методов. Не много запутанно, но суть сводиться к тому что мы может передавать метод в качестве аргумента и это все благодаря делегату. И при вызове основного метода, будет вызван делегат который указывает на свой метод, после того как метод отработал он возвращается в основной метод. Для большего понимания тонкостей в работе делегата, лучше использовать отладку и пошагово посмотреть как происходят вызовы методов.
Следующий пример показывает как с помощью делегата, используя анонимные методы и лямбда-метод вычислить арифметическое среднее:
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();
}
}
Как использовать дженерики внутри делегатов?
Приведенный пример ниже, покажет как использовать в делегатах дженерики:
//Объявляем делегат
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 встроенный, для этого посмотрим код ниже:
//Основной класс программы
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 всегда будут те что принимает делегат, а последним то что отдает.
Что такое анонимный делегат?
Это способ записи методов, а нужен он лишь для того что бы сократить функциональный код вашей программы. Посмотрим пример выше и используем анонимный делегат:
//Основной класс программы
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 только назвал его иначе, и воспользовался нашим функционалом вычисления так как мы его передали сами.
Как использовать Лямда-выражение в делегатах?
Лямда -выражения это стиль записи, который позволяет еще больше сократить код программы. Мы это и сделаем из нашего примера:
//Основной класс программы
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
В конце статьи хочу привести еще один пример основанный на предыдущих, но демонстрирует сумму двух чисел, в не зависимости от его типа данных.
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();
}
}
Еще хотело упомянуть об одной особенности лямбда-выражения в делегатах так называемом Замыкании.
Мы знаем что при использовании делегатов в лямбда-выражении, создается анонимный метод который принимает и производит вычислений, при использовании полей и локальных переменных, создаются ссылки на них и они используются как анонимные методы. Вы спросите о чем я? Сейчас все увидите на примере ниже:
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 из цикла. А как же использовать тогда этот вариант правильно, для этого надо завести локальную переменную в цикле, ее и передавать, рассмотрим пример:
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
