У меня есть класс, который обертывает другой класс и выдает несколько событий из класса, который он обертывает. (Экземпляр, который он обертывает, может измениться)
Я использовал следующий код:
public event EventHandler AnEvent;
public OtherClass Inner {
get { /* ... */ }
set {
//...
if(value != null)
value.AnEvent += AnEvent;
//...
}
}
Однако события были подняты непоследовательно.
Что не так с этим кодом?
Проблема заключается в том, что Delegate
являются неизменяемыми.
Если вы добавляете обработчик в событие, он создает новый экземпляр Delegate
, который содержит старые обработчики и новый обработчик. Старый Delegate
не изменяется и отбрасывается.
Когда я пишу, value.AnEvent += AnEvent
, он добавляет Delegate
, содержащий текущие обработчики (если есть) во внутреннее событие класса. Однако изменения внешнего события класса игнорируются, поскольку они не меняют экземпляр Delegate
, который я добавил во внутреннее событие класса. Аналогично, если я удаляю обработчик после установки свойства Inner
, обработчик не будет удален из внутреннего события класса.
Есть два правильных способа сделать это.
Я могу создать свой собственный обработчик, который вызывает событие-оболочку, например:
public event EventHandler AnEvent;
public OtherClass Inner {
get { /* ... */ }
set {
if(Inner != null)
Inner.AnEvent -= Inner_AnEvent;
//...
if(value != null)
value.AnEvent += Inner_AnEvent;
//...
}
}
void Inner_AnEvent(object sender, EventArgs e) {
var handler = AnEvent;
if (handler != null) handler(sender, e);
}
Другим способом является создание настраиваемого события в оболочке, которое добавляет его обработчики во внутреннее событие класса, например:
EventHandler anEventDelegates
public OtherClass Inner {
get { /* ... */ }
set {
//...
if(value != null)
value.AnEvent += anEventDelegates;
//...
}
}
public event EventHandler AnEvent {
add {
anEventDelegates += value;
if (Inner != null) Inner.AnEvent += value;
}
remove {
anEventDelegates -= value;
if(Inner != null) Inner -= value;
}
}
Обратите внимание, что это не полностью безопасно для потоков.
Я сам решил эту проблему и задаю вопрос и ответ на благо людей с аналогичными проблемами.
ваш ответ - здесь есть две проблемы...
Во-первых: в обоих случаях вы поднимаете внешнее событие с неверным отправителем. Кто-то, кто подписывается на событие на внешнем классе, ожидает, что эти классы будут подняты с отправителем этого внешнего класса.
Это особенно важно в таких элементах, как winform Controls или реализации списка привязок, где отправитель используется для идентификации объекта между многими, которые используют обработчик.
Это должно быть чем-то вроде:
void Inner_AnEvent(object sender, EventArgs e) {
var handler = AnEvent;
if (handler != null) handler(this, e);
}
Вторая (гораздо более незначительная) проблема заключается в том, что вы сейчас вынимаете событие во внутреннем классе, даже если у внешнего класса нет подписчиков. Вы можете исправить это с помощью более удобной обработки...
private EventHandler anEvent;
public event EventHandler AnEvent {
add { // note: not synchronized
bool first = anEvent == null;
anEvent += value;
if(first && anEvent != null && inner != null) {
inner.SomeEvent += Inner_AnEvent;
}
}
remove { // note: not synchronized
bool hadValue = anEvent != null;
anEvent -= value;
if(hadValue && anEvent == null && inner != null) {
inner.SomeEvent -= Inner_AnEvent;
}
}
}
(и аналогичный код в Inner get/set для подписки, если у нас есть слушатели...
if(value != null && anEvent != null)
value.AnEvent += Inner_AnEvent;
Это может быть большой заставкой, если у вас много экземпляров внешнего класса, но редко используется событие.