Разбираемся с делегатами

Приветствую всех, сегодня хотел затронуть тему о делегатах, и их применения.

Для работы с делегатами нам необходимо объявить тип делегата и тип указателей на метод оно и будет именем делегата.

	
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

 

 

Обновлено: 22.03.2018 — 20:16

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.