代码之家  ›  专栏  ›  技术社区  ›  SLaks

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

  •  2
  • SLaks  · 技术社区  · 15 年前

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

    我使用了以下代码:

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

    然而,事件的提出前后不一致。

    这个代码怎么了?

    2 回复  |  直到 15 年前
        1
  •  3
  •   SLaks    15 年前

    问题是 Delegate S是不变的。

    如果向事件添加处理程序,它将创建一个新的 代表 包含旧处理程序和新添加的处理程序的实例。老年人 代表 未修改并被丢弃。

    当我写作的时候, value.AnEvent += AnEvent 它增加了 代表 包含内部类事件的当前处理程序(如果有)。但是,对外部类事件的更改将被忽略,因为它们不会更改 代表 我添加到内部类事件的实例。同样,如果在设置 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
  •  3
  •   Community CDub    7 年前

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

    第一:在这两种情况下,您都是用错误的发送者引发外部事件。订阅外部类上的事件的人希望这些类使用 发件人 是那个阶级的。

    这在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;
    

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