Сегодня речь пойдет об очень интересной и полезной контсрукции языка программирования 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("Машину заглушили"); };
Как видите разницы нет и в целом не все так сложно. Какой подход выбрать, думайте сами. Всем спасибо.