Різниця між подіями та делегатами та відповідними додатками [закрито]


107

Я не бачу переваг використання подій над делегатами, крім синтаксичного цукру. Можливо, я нерозумію, але здається, що подія - це просто заповнення делегата.

Ви б пояснили мені відмінності і коли їх використовувати? Які переваги та недоліки? Наш код сильно вкорінений у подіях, і я хочу дійти до цього.

Коли ви використовуєте делегатів для подій і навпаки? Вкажіть, будь ласка, свій реальний досвід із обома, скажімо у виробничому коді.


Так, обгортати мою голову навколо відмінностей було дуже важко, вони виглядають однаково і, здається, роблять те саме на перший погляд
Роберт Гулд

1
Дивіться також це питання .
Димитрій К.

1
Різниця між двома подіями та делегатами - це факт, а не думка. Питання задає відповідні програми, оскільки вони ілюструють різницю проблем, які вирішують технології. Це також не питання думки, тому що ніхто не запитував, що найкраще. Жодна частина цього питання не є питанням думки, і це твердження також не є думкою. На мою думку. Ви отримали свій бейдж?
Пітер

Відповіді:


49

З технічної точки зору, інші відповіді вирішили розбіжності.

З точки зору семантики, події - це дії, порушені об'єктом, коли певні умови виконуються. Наприклад, у мого акціонерного класу є властивість під назвою Limit, і це спричиняє подію, коли ціни акцій досягають Ліміту. Це повідомлення здійснюється через подію. Невже хтось насправді піклується про цю подію і підписався на неї, це не стосується класу власника.

Делегат - це більш загальний термін для опису конструкції, схожої на покажчик у термінах C / C ++. Усі делегати в .Net - це делегати з декількома розсилками. З точки зору семантики вони, як правило, використовуються як своєрідні дані. Зокрема, вони є ідеальним способом реалізації стратегії . Наприклад, якщо я хочу сортувати Список об'єктів, я можу надати стратегію порівняння методу, щоб розповісти реалізації, як порівняти два об'єкти.

Я використовував два методи у виробничому коді. Тони моїх об’єктів даних сповіщають, коли певні властивості виконуються. Найбільш базовий приклад, щоразу, коли властивість змінюється, виникає подія PropertiesChanged (див. Інтерфейс INotifyPropertyChanged). Я використовував делегатів у коді, щоб запропонувати різні стратегії перетворення певних об'єктів у рядок. Цей конкретний приклад був прославленим списком реалізацій ToString () для певного типу об'єкта для відображення його користувачам.


4
Можливо, мені щось не вистачає, але чи не обробник події є типом делегата?
Powerlord

1
Моя відповідь стосується питань редагування №1 та №2; відмінності з точки зору використання. Для цілей цієї дискусії вони різні, хоча з технічної точки зору ви праві. Подивіться на інші відповіді щодо технічних відмінностей.
Szymon Rozga

3
"Усі делегати. Навіть делегати, які повертають цінності?
Qwertie

5
Так. Для історії погляньте на msdn.microsoft.com/en-us/magazine/cc301816.aspx . Перевірте: msdn.microsoft.com/en-us/library/system.delegate.aspx . Якщо вони повертають значення, значення, яке повертається, - це оцінка останнього делегата ланцюга.
Szymon Rozga

делегати - це типи посилань, які вказують на обробники подій, визначені в класі підписника. В інших словах, делегат використовується як зв'язок між подією (у видавці) та обробником події, визначеним у підписника. У програмі буде кілька підписників, які потребують прослуховування події, і в таких сценаріях делегати пропонують нам ефективний спосіб зв’язати видавця та передплатників.
josepainumkal

55

Ключове слово eventє модифікатором обсягу для делегатів багатоадресних повідомлень. Практичні відмінності між цим і лише декларуванням делегата багатоадресних повідомлень такі:

  • Ви можете використовувати eventв інтерфейсі.
  • Доступ до делегата багатоадресних повідомлень обмежений класом декларування. Поведінка така, нібито делегат був приватним для виклику. Для цілей призначення доступ має такий, який визначений явним модифікатором доступу (наприклад public event).

Як цікаво, ви можете застосувати +і -до делегатів з різними розсилками, і це основа +=і -=синтаксису для комбінованого призначення делегатів подіям. Ці три фрагменти еквівалентні:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Зразок другий, що ілюструє як пряме призначення, так і комбіноване призначення.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Зразок третій: більш знайомий синтаксис. Ви, мабуть, ознайомлені із призначенням null для видалення всіх обробників.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Як і властивості, події мають повний синтаксис, який ніхто ніколи не використовує. Це:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... робить точно так само, як це:

class myExample 
{
  public event EventHandler OnSubmit;
}

Методи додавання та видалення помітніші в досить витонченому синтаксисі, який використовує VB.NET (відсутність перевантажень оператора).


6
+ для "Доступ до делегата багатоадресної передачі обмежений класом декларування" - це для мене ключова різниця між делегатами та подіями.
РічардОД

2
Ще одна важлива відмінність (згадана itowlson нижче) полягає в тому, що не можна скасувати передплату всіх обробників подій шляхом присвоєння події, але вони могли це зробити з делегатом. (До речі, ваша корисна відповідь була для мене з усіх цих).
Роман Старков

4
Настільки ж зручно, як Google та stackoverflow, все це та багато іншого доступне з урахуванням вражаючих деталей у специфікаціях мови C #, доступних у відкритому доступі від Microsoft безкоштовно. Я знаю, що з цього приводу бог створив посібник, і Джон Скіт проковтнув його, але є й інші примірники :)
Peter Wone

12

Події - синтаксичний цукор. Вони дуже смачні. Коли я бачу подію, я знаю, що робити. Коли я бачу делегата, я не такий впевнений.

Поєднання подій з інтерфейсами (більше цукру) сприяє перекушування ротовою порожниною. Делегати та чисті віртуальні абстрактні класи набагато менш апетитні.


саме так я і бачу це. Я хочу більш глибокого і солодкого пояснення :)

13
Однак занадто багато цукру робить один жир ... = P
Ерік Форбс

5

Події позначені як такі у метаданих. Це дозволяє таким чином, як дизайнери Windows Forms або ASP.NET, відрізняти події від власних властивостей типу делегата та надавати їм відповідну підтримку (конкретно показуючи їх на вкладці "Події" у вікні "Властивості".

Ще одна відмінність від властивості типу делегата полягає в тому, що користувачі можуть додавати та видаляти обробники подій, тоді як за властивості типу делегата вони можуть встановлювати значення:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Це допомагає виділити передплатників подій: я можу додати свого обробника до події, а ви можете додати свого обробника до тієї ж події, і ви випадково не перезаписаєте мого обробника.


4

Незважаючи на те, що події, як правило, реалізуються разом з делегатами з декількох повідомлень, немає жодної вимоги, щоб вони використовувалися таким чином. Якщо клас виставляє подію, це означає, що клас відкриває два методи. Їх значення, по суті, є:

  1. Ось делегат. Будь ласка, посилайтеся на це, коли трапляється щось цікаве.
  2. Ось делегат. Ви повинні знищити всі посилання на нього, як тільки зручніше (і більше не називати це).

Найпоширеніший спосіб для класу обробляти подію, яку він піддається, - це визначити делегата багатоадресної передачі та додати / видалити будь-які делегати, які передаються вищевказаним методам, але немає жодної вимоги, що вони працюють таким чином. На жаль, архітектура подій не зможе зробити деякі речі, які б зробили альтернативні підходи набагато більш чистими (наприклад, метод підписки повертає MethodInvoker, який зберігатиме підписник; щоб скасувати підписку на подію, просто викликати повернений метод), тому делегати з кількома розсилками на сьогоднішній день є найбільш поширеним підходом.


4

щоб зрозуміти відмінності, ви можете переглянути ці 2 приклади

Приклад з Делегатами (Дія в цьому випадку є делегатом, який не повертає значення)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

щоб скористатися делегатом, вам слід зробити щось подібне

Animale 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().

Щоб уникнути цих слабких місць, ви можете використовувати eventsc #.

Ваш клас Animal зміниться таким чином

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

називати події

 Animale animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

Відмінності:

  1. Ви використовуєте не загальнодоступну власність, а загальнодоступне поле (із подіями, які компілятор захищає ваші поля від небажаного доступу)
  2. Події не можна призначити безпосередньо. У цьому випадку ви не можете зробити попередню помилку, яку я виявив, переважаючи поведінку.
  3. Ніхто за межами вашого класу не може підняти подію.
  4. Події можуть бути включені в декларацію інтерфейсу, тоді як поле не може

примітки

EventHandler оголошено таким делегатом:

public delegate void EventHandler (object sender, EventArgs e)

він приймає відправника (типу Object) та аргументи події. Відправник недійсний, якщо він походить із статичних методів.

Ви також можете використовувати EventHAndlerцей приклад замість цьогоEventHandler<ArgsSpecial>

тут зверніться до документації про EventHandler


3

Редагувати №1. Коли ви використовуєте делегатів для подій та vs.versa? Вкажіть, будь ласка, свій реальний досвід із обома, скажімо, у виробничому коді.

Коли я проектую власні API, я визначаю делегатів, які передаються як параметри методам або конструкторам класів:

  • Так що метод може реалізувати просту модель "шаблонного методу" (наприклад, Predicateі Actionделегати передаються до загальних класів колекції .Net)
  • Або так, що клас може робити зворотний дзвінок (як правило, зворотний виклик методу класу, який його створив).

Ці делегати, як правило, необов'язкові під час виконання (тобто не повинні бути null).

Я схильний не використовувати події; але там, де я використовую події, я використовую їх для необов'язкової сигналізації подій до нуля, одного або декількох клієнтів, які можуть бути зацікавлені, тобто коли є сенс, що клас (наприклад, System.Windows.Formклас) повинен існувати і запускати, чи є у когось клієнт чи ні додав до своєї події обробник подій (наприклад, існує подія "миші вниз" форми, але необов'язково, чи бажає будь-який зовнішній клієнт встановити обробник події на цю подію).


2

Хоча у мене немає жодних технічних причин, я використовую події в коді стилю інтерфейсу, іншими словами, у вищих рівнях коду та використовую делегати для логіки, яка лежить глибше в коді. Як я кажу, ви можете використовувати будь-яке, але я вважаю, що цей шаблон використання є логічно здоровим, якщо нічого іншого, він також допомагає документувати типи зворотних зворотів та їх ієрархію.


Редагувати: Я думаю, що різниця у моделях використання у мене полягатиме в тому, що я вважаю цілком прийнятним ігнорувати події, вони є гачками / заглушками, якщо вам потрібно знати про подію, слухайте їх, якщо вас не хвилює подія просто ігнорує це. Ось чому я використовую їх для інтерфейсу користувача, kindof Javascript / Browser стиль події. Однак, коли у мене є делегат, я дійсно очікую, що хтось впорається із завданням делегата, і викине виняток, якщо його не обробляють.


Ви б хотіли детальніше розглянути це, оскільки я також використовую евен в інтерфейсі? Гарного прикладу буде достатньо .... дякую

1

Різниця між подіями та делегатами набагато менша, ніж я раніше думав. Щойно я розмістив суперкоротке відео YouTube на цю тему: https://www.youtube.com/watch?v=el-kKK-7SBU

Сподіваюся, це допомагає!


2
Ласкаво просимо до переповнення стека! Хоча це теоретично може відповісти на питання, бажано було б сюди включити істотні частини відповіді та надати посилання для довідки.
GhostCat

1

Якщо ми використовуємо лише делегат замість події, тоді передплатник має можливість клонувати (), викликати () самого делегата, як показано нижче на зображенні. Що не правильно.

введіть тут опис зображення

У цьому головна відмінність ч / б події та делегата. абонент має лише одне право, тобто слухати події

Клас ConsoleLog передплачує події журналу через EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

Клас FileLog передплачує події журналу через EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

Операційний клас - це публікація подій журналу

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.