Подтвердить что ты не робот

С#: событие с экспликацией add/remove!= Типичное событие?

Я объявил общий обработчик событий

public delegate void EventHandler();

к которому я добавил метод расширения 'RaiseEvent':

public static void RaiseEvent(this EventHandler self)        {
   if (self != null) self.Invoke();
}

Когда я определяю событие с использованием типичного синтаксиса

public event EventHandler TypicalEvent;

то я могу вызвать метод расширения без проблем:

TypicalEvent.RaiseEvent();

Но когда я определяю событие с явным синтаксисом add/remove

private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}

тогда метод расширения не существует в событии, определенном с явным синтаксисом add/remove:

ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason

И когда я навещусь на событие, чтобы увидеть причину, в которой он говорит:

Событие 'ExplicitEvent' может только появляются в левой части + = или - =

Почему событие, определенное с использованием типичного синтаксиса, отличается от события, определенного с помощью явного синтаксиса добавления/удаления и почему методы расширения не работают над последним?

EDIT: я нашел, что могу обойти это, используя прямой обработчик событий:

_explicitEvent.RaiseEvent();

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

4b9b3361

Ответ 1

Потому что вы можете сделать это (это образец не-реального мира, но он "работает" ):

private EventHandler _explicitEvent_A;
private EventHandler _explicitEvent_B;
private bool flag;
public event EventHandler ExplicitEvent {
   add {
         if ( flag = !flag ) { _explicitEvent_A += value; /* or do anything else */ }
         else { _explicitEvent_B += value; /* or do anything else */ }
   } 
   remove {
         if ( flag = !flag ) { _explicitEvent_A -= value; /* or do anything else */ }
         else { _explicitEvent_B -= value; /* or do anything else */ }
   }
}

Как компилятор может знать, что он должен делать с "ExplicitEvent.RaiseEvent();"? Ответ: Он не может.

"ExplicitEvent.RaiseEvent();" это только синтаксический сахар, который может быть основан только на том случае, если событие неявно реализовано.

Ответ 2

Когда вы создаете событие типа "field-like", например:

public event EventHandler Foo;

компилятор генерирует поле и событие. В исходном коде класса, объявляющего событие, в любое время, когда вы ссылаетесь на Foo, компилятор понимает, что вы ссылаетесь на это поле. Однако поле является закрытым, поэтому в любое время, когда вы ссылаетесь на Foo из других классов, оно относится к событию (и, следовательно, к коду добавления/удаления).

Если вы объявите свой собственный явный код добавления/удаления, вы не получите автоматически сгенерированное поле. Итак, у вас есть только событие, и вы не можете поднять событие непосредственно на С# - вы можете только вызвать экземпляр делегата. Событие не является экземпляром делегата, это просто пара добавления/удаления.

Теперь ваш код содержит следующее:

public EventHandler TypicalEvent;

Это немного другое - это вовсе не объявление какого-либо события - оно объявляло публичное поле типа делегата EventHandler. Любой может вызвать это, потому что значение является просто экземпляром делегата. Важно понимать разницу между полем и событием. Вы никогда не должны писать такой код, так как я уверен, что у вас обычно нет публичных полей других типов, таких как string и int. К сожалению, это простая опечатка, и относительно сложно остановить ее. Вы заметили бы это, заметив, что компилятор разрешил вам назначать или использовать значение из другого класса.

Дополнительную информацию см. в статье о событиях и делегатах.

Ответ 3

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

private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}

private double seconds; 
public double Hours
{
    get { return seconds / 3600; }
    set { seconds = value * 3600; }
}

В обоих случаях член с свойством get/set или add/remove на самом деле не содержит никаких данных. Вам нужен "настоящий" частный член, чтобы содержать фактические данные. Свойства просто позволяют вам программировать дополнительную логику при экспонировании членов во внешний мир.

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

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

private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent 
{
   add 
   { 
       if (_explicitEvent == null) timer.Start();
       _explicitEvent += value; 
   } 
   remove 
   { 
      _explicitEvent -= value; 
      if (_explicitEvent == null) timer.Stop();
   }
}

Вероятно, вы захотите заблокировать добавление/удаление с помощью объекта (запоздалая мысль)...

Ответ 4

Объявление "plain" для TypicalEvent делает некоторые трюки компилятора. Он создает запись метаданных события, добавляет и удаляет методы и поле поддержки. Когда ваш код относится к TypicalEvent, компилятор переводит его в ссылку на поле поддержки; когда внешний код относится к TypicalEvent (используя + = и - =), компилятор переводит его в ссылку на метод добавления или удаления.

"Явное" объявление обходит этот обман компилятора. Вы указываете методы добавления и удаления и поле поддержки: действительно, как указывает TcK, может быть даже не поле поддержки (это обычная причина использования явной формы: см., Например, события в System.Windows.Forms.Control). Поэтому компилятор больше не может спокойно переводить ссылку на TypicalEvent в ссылку на поле поддержки: если вы хотите, чтобы поле поддержки, фактический объект-делегат, вам нужно напрямую ссылаться на поле поддержки:

_explicitEvent.RaiseEvent()