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

SLa*_*aks 4 .net c# delegates event-handling

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

我使用了以下代码:

public event EventHandler AnEvent;

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

然而,这些事件的提出并不一致。

这段代码有什么问题?

SLa*_*aks 6

问题是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);
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是在包装器中创建一个自定义事件,将其处理程序添加到内部类的事件中,如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 其中,我认为唯一语义正确的方法是第一种。即使除了“Sender”属性应该报告谁的问题(在接收订阅的对象不是发起操作的对象的情况下,这可能有些模糊),还有一个问题是,如果一个对象订阅了一个对象,会发生什么情况?方法调用内部类实例和外部类实例的事件,然后取消订阅其中一个。即使重复取消订阅一个实例的事件,也不应该取消订阅另一个实例的事件。 (2认同)

Mar*_*ell 6

你的答案-这里有两个问题......

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

这对于像 winform 控件或绑定列表实现这样的东西尤其重要,其中发送者用于识别共享处理程序的许多对象之间的对象。

这应该是这样的:

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

第二个(更小的)问题是,即使外部类没有订阅者,您当前也在内部类上取出一个事件。您可以通过更多的自定义处理来解决此问题...

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

if(value != null && anEvent != null)
    value.AnEvent += Inner_AnEvent;
Run Code Online (Sandbox Code Playgroud)

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