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

4
我有一个类,它包装另一个类并公开了它所包装的类中的几个事件。(它所包装的实例可能会发生改变)
我使用了以下代码:
public event EventHandler AnEvent;

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

然而,这些事件的上升不一致。

这段代码有什么问题?

2个回答

6
问题在于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
其中,我认为唯一语义上正确的方式是第一个。即使超越了“Sender”属性应该报告谁的问题(在接收订阅的对象不是发起操作的对象的情况下可能有些模糊),还存在一个问题,即如果一个对象订阅了内部和外部类实例的事件方法,然后取消订阅其中一个,那么应该发生什么。即使重复取消订阅一个实例的事件,也不应该取消订阅另一个实例的事件。 - supercat

6

你的回答——这里有两个问题...

第一个:在这两种情况下,你都使用了错误的发送者来提升外部事件。订阅外部类事件的人会期望那些类被提升时的发送者是外部类。

这在诸如winform控件或绑定列表实现等方面尤为重要,因为发送者用于标识共享处理程序的众多对象之间的对象。

应该改为以下内容:

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;

如果您有许多外部类的实例,但很少使用该事件,则此方法可能会节省大量时间。


我不是在编写一个库;我是唯一处理事件的人,并且我希望发送者参数是内部类。 - SLaks
我的第二个解决方案避免了第二个问题,这就是为什么我更喜欢它的原因。(如果没有订阅者,anEventDelegate将为空) - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接