Сегодня речь пойдет об очень интересной и полезной контсрукции языка программирования C# как delegate. Я постараюсь простыми словами объяснить что это такое и как их использовать.
Так что же такое delegate? Если объяснять простыми словами, то это такой же тип как и int, string и другие, который создан для возможности передавать методы в качестве аргументов функций. Если у Вас возникает вопрос зачем нужно передавать функции как аргументы, то ответ очень простой. В первую очередь посмотрите как реализованы все события в C#. Так же если вы работали с асинхронным кодом (не важно где), то Вы должны знать, что такое callback функции (функции которые вызываются после окончания какого-либо действия, например после получения ответа на POST запрос).
Рассмотрим простой пример создания своего собсвенного типа функции для события. Например у нас есть класс машина, которая может быть в заведенном состоянии, либо нет. Если машина в заведенном состоянии, то она едет с некоторой случайной скоростью. Мы же хотим отлавливать в коде события завода машины, а так же когда ее скорость равна нулю. При этом мы будем избезать такого слова как EventHandler.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace Emulator
{
class Car: IDisposable
{
//Перечисляем возможные состояния двигателя
public enum EngineState { On, Off }
private Random _rnd = new Random();
protected double _speed = .0;
protected EngineState _engineState = EngineState.Off;
protected bool _disposed = false;
private const double _MAX_SPEED = 180.0;
protected virtual void _OnRoad()
{
while(_engineState == EngineState.On)
{
_speed = _speed + _rnd.NextDouble() * (_rnd.Next(1, 10) / 2 == 0 ? 1 : -1);
if(_speed < 0)
{
_speed = 0;
}
else if(_speed > _MAX_SPEED)
{
_speed = _MAX_SPEED;
}
if (OnSpeedZeroEvent != null && _speed == 0)
{ //если событие нулевой скорости инициализировано
//вызываем его
OnSpeedZeroEvent(this, _engineState);
}
Thread.Sleep(1000);
}
_speed = .0;
if (OnSpeedZeroEvent != null)
{
OnSpeedZeroEvent(this, _engineState);
}
}
//Заводим машину
public void Go()
{
switch(_engineState)
{
case EngineState.On:
//тут мы будем издавать страшный звук.
//Я думаю Вы поняли о чем я :)
break;
case EngineState.Off:
_engineState = EngineState.On;
new Thread(_OnRoad).Start();
if (OnEngineStartEvent != null)
{ //если событие старта дивигателя инициализировано
//вызываем его
OnEngineStartEvent(this);
}
break;
}
}
//Глушим машину
public void Stop()
{
_engineState = EngineState.Off;
}
//Объявляем делегат (стурктуру функции) который мы будем использовать для
//передачи в качестве аргумента в событие завода машины
public delegate void OnEngineStart(Car sender);
//Создаем событие завода машины
public event OnEngineStart OnEngineStartEvent = null;
//Объявляем делегат (стурктуру функции) который мы будем использовать для
//передачи в качестве аргумента в событие остановки машины
public delegate void OnSpeedZero(Car sender, EngineState state);
//Создаем событие остановки машины (скорость = 0)
public event OnSpeedZero OnSpeedZeroEvent = null;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(_disposed)
{
return;
}
if(disposing)
{
Stop();
}
_disposed = true;
}
}
}
Как мы видим объявлено два делегата OnEngineStart и OnSpeedZero которые описывают структуру функций. В первом случае это функция, которая принимает один аргумент, во втором два аргумента. Напишем простую программу которая будет демонстрировать работу нашего класса.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Emulator
{
class Program
{
static void Main(string[] args)
{
Car car = new Car();
//инициализировали событие завода машины
car.OnEngineStartEvent += car_OnEngineStartEvent;
//инициализировали событие остановки с помощью лямда выражения
//разницы в работе нет, но позволяет избежать создание дополнительной функции
car.OnSpeedZeroEvent += (carObject, carState) =>
{
Console.WriteLine("Машина остановилась. Состояние двигателя {0}.", carState);
};
string command;
while ((command = Console.ReadLine().Trim().ToLower()) != "exit")
{
switch(command)
{
case "run":
car.Go();
break;
case "stop":
car.Stop();
break;
}
}
}
static void car_OnEngineStartEvent(Car sender)
{
Console.WriteLine("Машина завелась");
}
}
}
Все это хорошо, но порой огромное количество делегатов Вас может начать раздражать. Поэтому разработчики .NET, возможно будет сказано криво, создали «оболочку» для delegate и реализовали делегат Action. Он позволяет создавать структуру функции на лету. Давайте дополним наш класс событием отключения двигателя с помощью Action.
//Объявляем делегат (стурктуру функции) который мы будем использовать для
//передачи в качестве аргумента в событие завода машины
public delegate void OnEngineStart(Car sender);
//Создаем событие завода машины
public event OnEngineStart OnEngineStartEvent = null;
//Создаем событие выключения двигателя. В качестве делегата выступает функция которая принимает один аргумент типа Car. Если бы нам нужно было передатьвать
//еще дополнительные аргументы, например int и string, запись бы выглядела как public event Action<Car, int, string> OnEngineStoptEvent = null;
public event Action<Car> OnEngineStoptEvent = null;
//Глушим машину
public void Stop()
{
_engineState = EngineState.Off;
if(OnEngineStoptEvent != null)
{
OnEngineStoptEvent(this);
}
}
Переходим к программе. Тут все делает по полной аналогии.
car.OnEngineStoptEvent += (carObject) =>
{
Console.WriteLine("Машину заглушили");
};
Как видите разницы нет и в целом не все так сложно. Какой подход выбрать, думайте сами. Всем спасибо.
