C #: событие с простотой добавить / удалить! = Типичное событие? - PullRequest
40 голосов
/ 19 июня 2009

Я объявил универсальный обработчик событий

public delegate void EventHandler();

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

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

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

public event EventHandler TypicalEvent;

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

TypicalEvent.RaiseEvent();

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: я нашел, что могу обойти это, используя непосредственный обработчик событий:

_explicitEvent.RaiseEvent();

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

Ответы [ 4 ]

50 голосов
/ 19 июня 2009

Когда вы создаете «похожее на поле» событие, например:

public event EventHandler Foo;

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

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

Теперь ваш код содержал это:

public EventHandler TypicalEvent;

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

См. Мою статью о событиях и делегатах для получения дополнительной информации.

35 голосов
/ 19 июня 2009

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

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 ();" является только синтаксическим сахаром, который можно предсказать, только если событие неявно реализовано.

8 голосов
/ 10 декабря 2010

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

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();
   }
}

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

1 голос
/ 19 июня 2009

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

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

_explicitEvent.RaiseEvent()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...