【问题标题】:Adding one event handler to another将一个事件处理程序添加到另一个
【发布时间】:2009-06-26 02:22:31
【问题描述】:

我有一个类,它包装了另一个类,并从它所包装的类中公开了几个事件。 (它包裹的实例可以改变)

我使用了以下代码:

public event EventHandler AnEvent;

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

但是,事件的引发不一致。

这段代码有什么问题?

【问题讨论】:

    标签: c# .net delegates event-handling


    【解决方案1】:

    your answer - 这里有两个问题...

    首先:在这两种情况下,您都在向错误的发件人提出外部事件。订阅外部类上的事件的人会期望这些类由该外部类的 sender 引发。

    这在诸如 winform 控件或绑定列表实现之类的东西中尤为重要,其中 sender 用于识别共享处理程序的许多对象之间的对象。

    这应该是这样的:

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

    (以及内部 get/set 中的类似代码仅在我们有侦听器时才订阅...

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

    如果您有很多外部类的实例,但很少使用该事件,这可能会节省很多。

    【讨论】:

    • 我不是在写库;我是唯一处理事件的人,我希望 sender 参数是内部类。
    • 我的第二个解决方案避免了第二个问题,这就是我喜欢它的原因。 (如果没有订阅者,则 EventDelegate 将为空)
    【解决方案2】:

    问题在于Delegates 是不可变的。

    如果您向事件添加处理程序,它会创建一个新的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;
        }
    }
    

    请注意,这并不完全是线程安全的。

    我自己解决了这个问题,并发布了问题和答案,以帮助有类似问题的人。

    【讨论】:

    • 其中,我认为唯一语义正确的方法是第一种。甚至除了“Sender”属性应该报告谁的问题(如果接收订阅的对象不是启动操作的对象,这可能会有些模糊),还有一个问题是如果对象订阅了会发生什么方法到内部和外部类实例的事件,然后取消订阅一个。即使重复取消订阅一个实例的事件也不应取消订阅另一个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-04
    • 1970-01-01
    • 1970-01-01
    • 2016-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多