简体   繁体   English

将一个事件处理程序添加到另一个

[英]Adding one event handler to another

I have a class which wraps another class and exposes several events from the class it's wrapping. 我有一个包装另一个类的类,并从包装的类中公开了几个事件。 (The instance it wraps can change) (它包装的实例可以更改)

I used the following code: 我使用以下代码:

public event EventHandler AnEvent;

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

However, the events were raised inconsistently. 但是,事件发生的不一致。

What's wrong with this code? 此代码有什么问题?

The problem is that Delegate s are immutable. 问题是Delegate是不可变的。

If you add a handler to an event, it creates a new Delegate instance which contains the old handlers and the newly added handler. 如果向事件添加处理程序,它将创建一个新的Delegate实例,其中包含旧的处理程序和新添加的处理程序。 The old Delegate is not modified and is discarded. 旧的Delegate未修改且被丢弃。

When I write, value.AnEvent += AnEvent , it adds the Delegate containing the current handlers (if any) to the inner class's event. 当我写value.AnEvent += AnEvent ,它将包含当前处理程序(如果有)的Delegate添加到内部类的事件中。 However, changes to the outer class's event are ignored because they don't change the Delegate instance that I added to the inner classes event. 但是,对外部类事件的更改将被忽略,因为它们不会更改我添加到内部类事件的Delegate实例。 Similarly, if I remove a handler after setting the Inner property, the handler isn't removed from the inner class's event. 同样,如果在设置Inner属性后删除处理程序,则该处理程序也不会从内部类的事件中删除。


There are two correct ways to do this. 有两种正确的方法可以做到这一点。

I can make my own handler that invokes the wrapper's event, like this: 我可以创建自己的处理程序来调用包装器的事件,如下所示:

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

The other way is to make a custom event in the wrapper that adds its handlers to the inner class's event, like this: 另一种方法是在包装器中创建一个自定义事件,将其处理程序添加到内部类的事件中,如下所示:

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

Note that this is not entirely thread-safe. 请注意,这并不完全是线程安全的。

I solved this problem myself and am posting the question & answer for the benefit of people with similar problems. 我自己解决了这个问题,并发布了问题和答案,以帮助有类似问题的人。

The your answer - there are two problems here... 您的答案 -这里有两个问题...

First: in both cases, you are raising the outer event with the wrong sender. 第一:在两种情况下,您都使用错误的发件人引发外部事件。 Someone subscribing to an event on the outer class would expect those classes to be raised with a sender of that outer class. 订阅外部类事件的人会希望这些类由该外部类的发送者引发。

This is particularly important in things like winform Controls, or binding-list implementations, where the sender is used to identify the object between many that share a handler. 这在Winform Controls或绑定列表实现之类的情况下尤其重要,在该实现中, 发送方用于标识共享处理程序的许多对象之间的对象。

This should instead be something like: 相反,它应该类似于:

void Inner_AnEvent(object sender, EventArgs e) { 
    var handler = AnEvent;
    if (handler != null) handler(this, e);
}

The second (much more minor) issue is that you are currently taking out an event on the inner class even if the outer class has no subscribers. 第二个问题(更为次要的问题)是,即使外部类没有订阅者,您当前也正在对内部类进行事件处理。 You can fix this with a bit more custom handling... 您可以通过更多自定义处理来解决此问题...

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

(and similar code in the Inner get/set to only subscribe if we have listeners... (以及内部获取/设置中的类似代码仅在我们有监听器的情况下才能订阅...

if(value != null && anEvent != null)
    value.AnEvent += Inner_AnEvent;

This might be a big saver if you have lots of instances of the outer-class, but rarely use the event. 如果您有很多外部类的实例,但是很少使用该事件,那么这可能会节省很多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM