Добавление одного обработчика событий в другой

2

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

Я использовал следующий код:

public event EventHandler AnEvent;

public OtherClass Inner {
    get { /* ... */ }
    set {
        //...
        if(value != null)
            value.AnEvent += AnEvent;
        //...
    }
}

Однако события были подняты непоследовательно.

Что не так с этим кодом?

Теги:
delegates
event-handling

2 ответа

3
Лучший ответ

Проблема заключается в том, что 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;
    }
}

Обратите внимание, что это не полностью безопасно для потоков.

Я сам решил эту проблему и задаю вопрос и ответ на благо людей с аналогичными проблемами.

  • 2
    Из них, я думаю, единственный семантически правильный путь - первый. Даже помимо вопросов, о которых должно сообщать свойство «Отправитель» (что может быть несколько мутно в случаях, когда объект, который получает подписку, не является объектом, инициирующим действие), также возникает вопрос о том, что должно произойти, если объект подписывается на метод для события как внутреннего, так и внешнего экземпляра класса, а затем отписывает один. Даже повторные отписки от одного события не должны отписываться от другого.
3

ваш ответ - здесь есть две проблемы...

Во-первых: в обоих случаях вы поднимаете внешнее событие с неверным отправителем. Кто-то, кто подписывается на событие на внешнем классе, ожидает, что эти классы будут подняты с отправителем этого внешнего класса.

Это особенно важно в таких элементах, как 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;

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

  • 0
    Я не пишу библиотеку; Я единственный, кто обрабатывает события, и я хочу, чтобы параметр отправителя был внутренним классом.
  • 0
    Вторая проблема устраняется моим вторым решением, поэтому я предпочитаю его. (anEventDelegate будет нулевым, если нет подписчиков)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню