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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

Ключевое слово 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(без перегрузки оператора).

Ответ 3

События - это синтаксический сахар. Они восхитительны. Когда я вижу событие, я знаю, что делать. Когда я вижу делегата, я не уверен.

Объединяя события с интерфейсами (больше сахара), вы можете перекусить. Делегаты и чистые виртуальные абстрактные классы гораздо менее аппетитные.

Ответ 4

События отмечены как таковые в метаданных. Это позволяет таким элементам, как дизайнеры Windows Forms или ASP.NET, отличать события от простых свойств типа делегата и предоставлять им соответствующую поддержку (в частности, показывать их на вкладке "События" в окне "Свойства" ).

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

Хотя события обычно реализуются с делегатами многоадресной рассылки, нет требования, чтобы они использовались таким образом. Если класс предоставляет событие, это означает, что класс предоставляет два метода. Их значения, по существу:

  1. Здесь делегат. Пожалуйста, вызовите его, когда произойдет что-то интересное.
  2. Здесь делегат. Вы должны уничтожить все ссылки на него как можно скорее (и больше не называть его).

Наиболее распространенным способом для класса обрабатывать событие, которое он предоставляет, является определение делегата многоадресной рассылки и добавление/удаление любых делегатов, которые передаются этим методам, но нет необходимости, чтобы они работали таким образом. К сожалению, архитектура событий не может сделать некоторые вещи, которые сделали бы альтернативные подходы более чистыми (например, метод подписки возвращает MethodInvoker, который будет храниться подписчиком, чтобы отменить подписку на событие, просто вызвать возвращаемый метод), так что делегаты многоадресной передачи являются наиболее распространенным подходом.

Ответ 7

чтобы понять различия, вы можете посмотреть на это 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 в С#.

Ваш класс 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();

Отличия:

  • Вы не используете публичное свойство, а общедоступное поле (с событиями, которые компилятор защищает ваши поля от нежелательного доступа).
  • События не могут быть напрямую назначены. В этом случае вы не можете выполнить предыдущую ошибку, которую я показал с переопределением поведения.
  • Никто из вашего класса не может поднять это событие.
  • События могут быть включены в декларацию интерфейса, тогда как поле не может

заметки

EventHandler объявляется следующим делегатом:

public delegate void EventHandler (object sender, EventArgs e)

требуется отправитель (типа объекта) и аргументы событий. Отправитель имеет значение null, если он исходит из статических методов.

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

отсылайте здесь для документации о EventHandler

Ответ 8

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


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

Ответ 9

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

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