Які відмінності між делегатами та подіями? Не мають обох посилань на функції, які можна виконати?
Які відмінності між делегатами та подіями? Не мають обох посилань на функції, які можна виконати?
Відповіді:
Event декларація додає шар абстракції і захистів на делегат , наприклад. Цей захист заважає клієнтам делегата скидати делегата та його список викликів і дозволяє лише додавати або видаляти цілі зі списку викликів.
Щоб зрозуміти відмінності, ви можете переглянути ці 2 приклади
Приклад з Делегатами (у цьому випадку Дія - це певний делегат, який не повертає значення)
public class Animal
{
public Action Run {get; set;}
public void RaiseEvent()
{
if (Run != null)
{
Run();
}
}
}
Щоб використовувати делегата, вам слід зробити щось подібне:
Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();
Цей код працює добре, але у вас можуть бути слабкі місця.
Наприклад, якщо я напишу так:
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;
з останнього рядка коду я замінив попередню поведінку лише на одній відсутній +
(я використав =
замість +=
)
Ще одне слабке місце полягає в тому, що кожен клас, який використовує ваш Animal
клас, може підняти його RaiseEvent
просто animal.RaiseEvent()
.
Щоб уникнути цих слабких місць, ви можете використовувати events
c #.
Ваш клас Animal зміниться таким чином:
public class ArgsSpecial : EventArgs
{
public ArgsSpecial (string val)
{
Operation=val;
}
public string Operation {get; set;}
}
public class Animal
{
// Empty delegate. In this way you are sure that value is always != null
// because no one outside of the class can change it.
public event EventHandler<ArgsSpecial> Run = delegate{}
public void RaiseEvent()
{
Run(this, new ArgsSpecial("Run faster"));
}
}
називати події
Animal animal= new Animal();
animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
animal.RaiseEvent();
Відмінності:
Примітки:
EventHandler оголошено таким делегатом:
public delegate void EventHandler (object sender, EventArgs e)
він приймає відправника (типу Object) та аргументи події. Відправник недійсний, якщо він походить із статичних методів.
Цей приклад, який використовує EventHandler<ArgsSpecial>
, також можна записати, використовуючи EventHandler
натомість.
Зверніться тут для документації про EventHandler
RaiseEvent
, якщо метод виклику має доступ до екземпляра animal
коду, який використовує подію?
animal.Run(this, new ArgsSpecial("Run faster");
?
Окрім синтаксичних та експлуатаційних властивостей, є ще й семантична різниця.
Делегати концептуально є шаблонами функцій; тобто вони виражають договір, якого повинна дотримуватися функція, щоб вважатись "типом" делегата.
Події представляють ... ну, події. Вони покликані попередити когось, коли щось трапиться, і так, вони дотримуються делегатського визначення, але вони не те саме.
Навіть якби вони були абсолютно однаковими (синтаксично і в коді ІЛ), все одно залишиться семантична різниця. Взагалі я вважаю за краще мати два різних імені для двох різних концепцій, навіть якщо вони реалізовані однаково (що не означає, що я люблю мати один і той же код двічі).
Ось ще одне хороше посилання на яке можна посилатися. http://csharpindepth.com/Articles/Chapter2/Events.aspx
Коротко кажучи, вилучення із статті - Події - це інкапсуляція над делегатами.
Цитата зі статті:
Припустимо, події не існували як концепція в C # /. NET. Як інший клас підписався на подію? Три варіанти:
Публічна змінна делегата
Змінна делегата, яку підтримує властивість
Змінна делегата методами AddXXXHandler та RemoveXXXHandler
Варіант 1 явно жахливий, тому що ми з усіх причин нормально прикриваємося загальнодоступними змінними.
Варіант 2 трохи кращий, але дозволяє абонентам ефективно перекривати один одного - було б надто просто написати someInstance.MyEvent = eventHandler; що замінить будь-які наявні обробники подій, а не додавання нового. Крім того, вам ще потрібно записати властивості.
Варіант 3 - це, в основному, те, що вам дають події, але з гарантованою умовою (згенерований компілятором і підкріплений додатковими прапорами в IL) та "безкоштовна" реалізація, якщо ви задоволені семантикою, яку дають вам польові події. Підписка на підписку та скасування підписки на події інкапсульована, не дозволяючи довільного доступу до списку обробників подій, а мови можуть спростити справи, надавши синтаксис як для декларації, так і для підписки.
public Delegate
змінний буде викриття «даних», але в міру моєї ООП знання ніколи не згадував які - або понять зовсім подібно Delegate
(це ні «об'єкт» , ні «повідомлення») , і .NET насправді ледве не обробляє делегатів, як дані.
AddXXXHandler
методів зі private Delegate
змінною може бути хорошим варіантом. У цьому випадку ви можете перевірити, чи обробник вже встановлений, і реагувати належним чином. Це також може бути хорошим налаштуванням, якщо вам потрібен об'єкт, у якому знаходиться, Delegate
щоб мати змогу очистити всі обробники ( event
не дає вам ніякого способу зробити це).
ПРИМІТКА. Якщо у вас є доступ до C # 5.0 Unleashed , прочитайте "Обмеження щодо простого використання делегатів" у главі 18 під заголовком "Події", щоб краще зрозуміти відмінності між ними.
Мені завжди допомагає простий, конкретний приклад. Отже ось один для громади. Спочатку я показую, як ви можете самостійно використовувати делегатів, щоб робити те, що події роблять для нас. Потім я показую, як те саме рішення буде працювати з екземпляром EventHandler
. А потім я пояснюю, чому ми НЕ хочемо робити те, що я пояснюю в першому прикладі. Цей пост був натхненний статтею Джона Скіта.
Приклад 1: Використання публічного делегата
Припустимо, у мене є програма WinForms з одним спадною панеллю. Випадаюче меню прив’язане до List<Person>
. Де Особа має властивості Id, Імені, Псевдоніма, HairColor. У головній формі - користувацьке управління користувачем, яке показує властивості цієї людини. Коли хтось вибирає людину у спадному меню на оновленнях керування користувача, щоб відобразити властивості вибраної людини.
Ось як це працює. У нас є три файли, які допомагають нам скласти це разом:
Ось відповідний код для кожного з класів:
class Mediator
{
public delegate void PersonChangedDelegate(Person p); //delegate type definition
public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
{
if (PersonChangedDel != null)
{
PersonChangedDel(p);
}
}
}
Ось наш контроль користувачів:
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
Mediator.PersonChangedDel += DetailView_PersonChanged;
}
void DetailView_PersonChanged(Person p)
{
BindData(p);
}
public void BindData(Person p)
{
lblPersonHairColor.Text = p.HairColor;
lblPersonId.Text = p.IdPerson.ToString();
lblPersonName.Text = p.Name;
lblPersonNickName.Text = p.NickName;
}
}
Нарешті, у нашому Form1.cs є наступний код. Тут ми викликаємо OnPersonChanged, який викликає будь-який код, підписаний на делегата.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}
Добре. Отже, так ви працюєте, не використовуючи події та використовуючи лише делегатів . Ми просто поміщаємо публічного делегата в клас - ви можете зробити його статичним або синглтонним, або будь-яким іншим. Чудово.
АЛЕ, АЛЕ, АЛЕ, ми не хочемо робити те, що я тільки що описав вище. Тому що громадські поля погані з багатьох, з багатьох причин. То які наші варіанти? Як описує Джон Скіт, ось наші варіанти:
PersonChangedDel = null
, витираючи всі інші підписки. Інша проблема, яка залишається тут, полягає в тому, що оскільки користувачі мають доступ до делегата, вони можуть викликати цілі в списку викликів - ми не хочемо, щоб зовнішні користувачі мали доступ до того, коли робити свої події.Цей третій варіант - це по суті те, що дає нам подія. Коли ми оголошуємо EventHandler, він надає нам доступ до делегата - не публічно, не як власність, але як цю річ ми називаємо подією, яка щойно додає / видаляє доступ.
Давайте подивимось, як виглядає та сама програма, але тепер використовуємо подію замість публічного делегата (я також змінив наш Посередник на одиночний):
Приклад 2: З EventHandler замість публічного делегата
Посередник:
class Mediator
{
private static readonly Mediator _Instance = new Mediator();
private Mediator() { }
public static Mediator GetInstance()
{
return _Instance;
}
public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.
public void OnPersonChanged(object sender, Person p)
{
var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
if (personChangedDelegate != null)
{
personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
}
}
}
Зауважте, що якщо ви F12 на EventHandler, він покаже вам, що визначення є лише загальним ідентифікованим делегатом з додатковим об'єктом "відправник":
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Контроль користувача:
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
}
void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
{
BindData(e.Person);
}
public void BindData(Person p)
{
lblPersonHairColor.Text = p.HairColor;
lblPersonId.Text = p.IdPerson.ToString();
lblPersonName.Text = p.Name;
lblPersonNickName.Text = p.NickName;
}
}
Нарешті, ось код Form1.cs:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}
Оскільки EventHandler хоче і EventArgs як параметр, я створив цей клас із лише одним властивістю в ньому:
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
Сподіваємось, це трохи показує, чому ми маємо події та чим вони відрізняються - але функціонально однакові - як делегати.
The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events
. В останній версії програми Mediator
ви все одно можете телефонувати OnPersonChange
кожен раз, коли маєте посилання на одиночку. Можливо, вам слід зазначити, що такий Mediator
підхід не заважає такої поведінки, і ближче до шини подій.
Ви також можете використовувати події в інтерфейсних деклараціях, не так для делегатів.
Action a { get; set; }
всередині інтерфейсу визначення.
Яке велике непорозуміння між подіями та делегатами !!! Делегат вказує TYPE (наприклад, a class
або an interface
), тоді як подія є лише своєрідною ЧЛЕНОЮ (наприклад, полями, властивостями тощо). І, як і будь-який інший тип учасника, подія також має тип. Однак у випадку події тип події повинен бути визначений делегатом. Наприклад, ви не можете оголосити подію типу, визначеного інтерфейсом.
Завершуючи, ми можемо зробити таке Спостереження: тип події ОБОВ'ЯЗКОВО визначається делегатом . Це основне співвідношення між подією і делегатом і описано в розділі II.18 визначення подій з ECMA-335 (CLI) Перегородки I до VI :
За типового використання TypeSpec (за наявності) ідентифікує делегата , підпис якого відповідає аргументам, переданим методу пожежі події.
Однак цей факт НЕ означає, що подія використовує резервне поле делегата . По правді, подія може використовувати резервне поле будь-якого іншого типу структури даних на ваш вибір. Якщо ви явно реалізуєте подію в C #, ви можете обирати спосіб зберігання обробників подій (зауважте, що обробники подій є екземплярами типу події , що, в свою чергу, є обов'язковим типом делегата --- від попереднього спостереження ). Але ви можете зберігати ці обробники подій (які є делегуючими екземплярами) у такій структурі даних, як a List
або a Dictionary
чи будь-який інший, або навіть у резервному полі делегата. Але не забувайте, що НЕ обов'язково використовувати поле делегата.
Подія в .net - це призначена комбінація методу Add і методу Remove, який очікує певного типу делегата. І C #, і vb.net можуть автоматично генерувати код для методів додавання та видалення, який визначатиме делегата для утримання підписок на події, а також додає / видаляє передане в делегаті до / з цього делегата підписки. VB.net також автоматично генерує код (із заявою RaiseEvent), щоб викликати список підписок, якщо і лише якщо він не порожній; чомусь C # не генерує останнього.
Зауважте, що хоча звичайно керувати підписками на події за допомогою делегата багатоадресної передачі, це не єдиний засіб для цього. З публічної точки зору, передбачуваний підписник подій повинен знати, як повідомити об’єкту, що він хоче отримувати події, але йому не потрібно знати, який механізм видавець використовуватиме для події подій. Зауважте також, що хоч хто визначав структуру даних про події в .net, мабуть, вважав, що має бути публічний засіб їх підняття, ні C #, ні vb.net не використовують цю функцію.
Щоб визначитись із подією просто:
Подія - СПРАВКА делегату з двома обмеженнями
Вище два - слабкі моменти для делегатів, і це вирішується у разі. Повний зразок коду, щоб показати різницю у скрипці, тут https://dotnetfiddle.net/5iR3fB .
Перемістіть коментар між подією та делегатом та кодом клієнта, який викликає / призначить значення для делегування, щоб зрозуміти різницю
Ось вбудований код.
/*
This is working program in Visual Studio. It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
Event is an delegate reference with two restrictions for increased protection
1. Cannot be invoked directly
2. Cannot assign value to delegate reference directly
Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/
public class RoomTemperatureController
{
private int _roomTemperature = 25;//Default/Starting room Temperature
private bool _isAirConditionTurnedOn = false;//Default AC is Off
private bool _isHeatTurnedOn = false;//Default Heat is Off
private bool _tempSimulator = false;
public delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
// public OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above),
public event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above),
public RoomTemperatureController()
{
WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
}
private void InternalRoomTemperatuerHandler(int roomTemp)
{
System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
}
//User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
public bool TurnRoomTeperatureSimulator
{
set
{
_tempSimulator = value;
if (value)
{
SimulateRoomTemperature(); //Turn on Simulator
}
}
get { return _tempSimulator; }
}
public void TurnAirCondition(bool val)
{
_isAirConditionTurnedOn = val;
_isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
}
public void TurnHeat(bool val)
{
_isHeatTurnedOn = val;
_isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
}
public async void SimulateRoomTemperature()
{
while (_tempSimulator)
{
if (_isAirConditionTurnedOn)
_roomTemperature--;//Decrease Room Temperature if AC is turned On
if (_isHeatTurnedOn)
_roomTemperature++;//Decrease Room Temperature if AC is turned On
System.Console.WriteLine("Temperature :" + _roomTemperature);
if (WhenRoomTemperatureChange != null)
WhenRoomTemperatureChange(_roomTemperature);
System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
}
}
}
public class MySweetHome
{
RoomTemperatureController roomController = null;
public MySweetHome()
{
roomController = new RoomTemperatureController();
roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
//roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
//roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
roomController.SimulateRoomTemperature();
System.Threading.Thread.Sleep(5000);
roomController.TurnAirCondition (true);
roomController.TurnRoomTeperatureSimulator = true;
}
public void TurnHeatOrACBasedOnTemp(int temp)
{
if (temp >= 30)
roomController.TurnAirCondition(true);
if (temp <= 15)
roomController.TurnHeat(true);
}
public static void Main(string []args)
{
MySweetHome home = new MySweetHome();
}
}
Делегат - безпечний для типу тип покажчик. Подія - це реалізація шаблону дизайну видавця-підписника за допомогою делегата.
Якщо ви поставите прапорець проміжну мову, ви дізнаєтесь, що компілятор .net перетворює делегата в закритий клас в IL з деякими вбудованими функціями, такими як виклик, beginInvoke, endInvoke та клас делегата, успадкований від іншого класу, може називатися "SystemMulticast". Я думаю, що Event - це дочірній клас Delegate з деякими додатковими властивостями.
Різниця між примірником події та делегатом полягає в тому, що ви не можете запускати подію поза декларацією. Якщо ви оголошуєте подію в класі А, ви можете запустити цю подію лише в класі А. Якщо ви оголосите делегата в класі А, ви можете використовувати цього делегата де завгодно. Я думаю, що це головна відмінність між ними