Разница между событиями и делегатами и соответствующими приложениями [закрыто]


107

Я не вижу преимуществ использования событий перед делегатами, кроме синтаксического сахара. Возможно, я неправильно понимаю, но кажется, что событие - это просто заполнитель для делегата.

Не могли бы вы объяснить мне различия и когда их использовать? Какие преимущества и недостатки? Наш код сильно связан с событиями, и я хочу разобраться в этом.

Когда бы вы использовали делегатов вместо событий и наоборот? Пожалуйста, укажите свой реальный опыт работы с обоими, скажем, в производственном коде.


Да, понять различия было действительно сложно, они выглядят одинаково и, кажется, с первого взгляда делают то же самое
Роберт Гулд

1
См. Также этот вопрос .
Dimitri C.

1
Разница между двумя событиями и делегатами - это факт, а не мнение. Вопрос касается соответствующих приложений, поскольку они иллюстрируют разницу в проблемах, которые решают технологии. Это тоже не вопрос мнения, потому что никто не спрашивал, что лучше. Никакая часть этого вопроса не является вопросом мнения, и это утверждение также не является мнением. По моему мнению. Вы получили свой значок?
Peter Wone

Ответы:


49

С технической точки зрения другие ответы касались различий.

С точки зрения семантики события - это действия, вызываемые объектом при выполнении определенных условий. Например, у моего класса Stock есть свойство Limit, и оно вызывает событие, когда цена акций достигает Limit. Это уведомление делается через событие. Независимо от того, действительно ли кто-нибудь заботится об этом событии и подписывается на него, класс-владелец не занимается.

Делегат - это более общий термин для описания конструкции, аналогичной указателю в терминах C / C ++. Все делегаты в .Net являются делегатами многоадресной рассылки. С точки зрения семантики они обычно используются как своего рода ввод. В частности, они являются прекрасным способом реализации паттерна стратегии . Например, если я хочу отсортировать список объектов, я могу предоставить методу стратегию Comparator, чтобы сообщить реализации, как сравнивать два объекта.

Я использовал два метода в производственном коде. Тонны моих объектов данных уведомляют, когда выполняются определенные свойства. Самый простой пример: всякий раз, когда свойство изменяется, возникает событие PropertyChanged (см. Интерфейс INotifyPropertyChanged). Я использовал делегаты в коде, чтобы предоставить различные стратегии преобразования определенных объектов в строку. Этот конкретный пример представлял собой прославленный список реализаций ToString () для определенного типа объекта, чтобы отображать его пользователям.


4
Может быть, я что-то упускаю, но разве обработчик событий не является типом делегата?
Powerlord,

1
Мой ответ касается вопросов Править №1 и №2; различия с точки зрения использования. Для целей этого обсуждения они разные, хотя с технической точки зрения вы правы. Взгляните на другие ответы на технические различия.
Szymon Rozga

3
«Все делегаты в .Net являются делегатами многоадресной рассылки»? Даже делегаты, возвращающие значения?
Qwertie 06

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
+ для «Вызов доступа к многоадресному делегату ограничен объявленным классом» - это для меня ключевое различие между делегатами и событиями.
RichardOD

2
Еще одно важное отличие (упомянутое ниже Итоулсоном) заключается в том, что нельзя отменить подписку на все обработчики событий, назначив событие, но они могут сделать это с помощью делегата. (Кстати, ваш ответ был для меня самым полезным из всех).
Роман Старков

4
Какими бы удобными ни были Google и stackoverflow, все это и многое другое доступно в скучных подробностях в спецификации языка C #, общедоступной бесплатно от Microsoft. Я знаю, что на первый взгляд это руководство создал бог, и Джон Скит проглотил его, но есть и другие копии :)
Питер Вон

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

Чтобы избежать этих слабых мест, вы можете использовать eventsв C #.

Ваш класс 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) и события. Отправитель имеет значение NULL, если он исходит из статических методов.

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

обратитесь сюда для документации о EventHandler


3

Редактировать # 1 Когда бы вы использовали делегатов над событиями и против Versa? Пожалуйста, укажите свой реальный опыт работы с обоими, скажем, в производственном коде.

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

  • Так что метод можно реализовать простой «шаблон метода» шаблона (как например, Predicateи Actionделегаты передаются в классы общего сбора .Net)
  • Или чтобы класс мог выполнять «обратный вызов» (обычно обратный вызов методу класса, который его создал).

Эти делегаты обычно не являются обязательными во время выполнения (т.е. не должны быть null).

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


2

Хотя у меня нет технических причин для этого, я использую события в коде стиля пользовательского интерфейса, другими словами, на более высоких уровнях кода и использую делегаты для логики, которая лежит глубже в коде. Как я уже сказал, вы можете использовать и то, и другое, но я считаю этот шаблон использования логически обоснованным, как минимум, он помогает документировать типы обратных вызовов и их иерархию.


Изменить: я думаю, что разница в шаблонах использования, которые у меня были, заключалась бы в том, что я считаю вполне приемлемым игнорировать события, они являются крючками / заглушками, если вам нужно знать о событии, слушайте их, если вам все равно событие просто игнорируйте. Вот почему я использую их для пользовательского интерфейса, вроде стиля событий Javascript / Browser. Однако, когда у меня есть делегат, я ДЕЙСТВИТЕЛЬНО ожидаю, что кто-то обработает задачу делегата, и выдаст исключение, если оно не обработано.


Не могли бы вы рассказать об этом подробнее, поскольку я также использую эвены в пользовательском интерфейсе? Достаточно хорошего примера .... спасибо

1

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

Надеюсь это поможет!


2
Добро пожаловать в Stack Overflow! Хотя теоретически это может дать ответ на вопрос, было бы предпочтительнее включить сюда основные части ответа и предоставить ссылку для справки.
GhostCat

1

Если мы используем только делегат вместо Event, тогда подписчик имеет возможность clone (), invoke () самого делегата, как показано ниже на изображении. Что не так.

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

В этом основное отличие ч / б мероприятия и делегата. у абонента есть только одно право - слушать события

Класс 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.