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

C# 将一个事件处理程序添加到另一个事件处理程序,c#,.net,delegates,event-handling,C#,.net,Delegates,Event Handling,我有一个类,它包装了另一个类,并公开了它包装的类中的几个事件。(它所包装的实例可以更改) 我使用了以下代码: public event EventHandler AnEvent; public OtherClass Inner { get { /* ... */ } set { //... if(value != null) value.AnEvent += AnEvent; //... } }

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

我使用了以下代码:

public event EventHandler AnEvent;

public OtherClass Inner {
    get { /* ... */ }
    set {
        //...
        if(value != null)
            value.AnEvent += AnEvent;
        //...
    }
}
然而,提出的事件前后不一致


此代码有什么问题?

问题在于
委托
是不可变的

如果向事件添加处理程序,它将创建一个新的
委托
实例,其中包含旧的处理程序和新添加的处理程序。旧的
委托
不会被修改并被丢弃

当我编写时,
value.AnEvent+=AnEvent
,它将包含当前处理程序(如果有)的
委托添加到内部类的事件中。但是,对外部类事件的更改将被忽略,因为它们不会更改我添加到内部类事件中的
委托
实例。类似地,如果在设置
internal
属性后删除处理程序,则不会从内部类的事件中删除该处理程序


有两种正确的方法可以做到这一点

我可以创建自己的处理程序来调用包装器的事件,如下所示:

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

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

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;
    }
}
请注意,这不是完全线程安全的

我自己解决了这个问题,为了有类似问题的人的利益,我发布了这个问题和答案。

这里有两个问题

第一:在这两种情况下,您都是用错误的发送者引发外部事件。订阅外部类上的事件的人会期望通过该外部类的发送者引发这些类

这在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;
        }
    }
}
(和内部get/set中类似的代码,如果我们有监听器,则只订阅…)

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

如果您有很多外部类的实例,但很少使用事件,这可能是一个很大的节约。

我不是在编写库;我是唯一处理事件的人,我希望sender参数是内部类。第二个问题可以通过我的第二个解决方案来避免,这就是我喜欢它的原因。(如果没有订阅者,anEventDelegate将为null)在这些方法中,我认为语义上唯一正确的方法是第一种。甚至除了“发送者”属性应该报告谁的问题(在接收订阅的对象不是发起操作的对象的情况下,这可能有些模糊)还有一个问题是,如果一个对象向一个内部和外部类实例的事件订阅了一个方法,然后又取消订阅了一个实例,那么会发生什么情况。即使重复取消订阅一个实例的事件,也不应该取消订阅另一个实例。