什么是最佳的事件冒泡方式?

24

我有三个对象,ObjectA拥有一个ObjectB,ObjectB又拥有一个ObjectC。当ObjectC触发一个事件时,我需要ObjectA知道它发生了什么,所以我已经这样做了...

public delegate void EventFiredEventHandler();

public class ObjectA
{
    ObjectB objB;

    public ObjectA()
    {
        objB = new ObjectB();
        objB.EventFired += new EventFiredEventHandler(objB_EventFired);
    }

    private void objB_EventFired()
    {
        //Handle the event.
    }
}

public class ObjectB
{
    ObjectC objC;

    public ObjectB()
    {
        objC = new ObjectC();
        objC.EventFired += new EventFiredEventHandler(objC_EventFired);
        objC.FireEvent();
    }

    public event EventFiredEventHandler EventFired;
    protected void OnEventFired()
    {
        if(EventFired != null)
        {
            EventFired();
        }
    }

    private void objC_EventFired()
    {
            //objC fired an event, bubble it up.
        OnEventFired();
    }
}

public class ObjectC
{
    public ObjectC(){}

    public void FireEvent()
    {
        OnEventFired();
    }

    public event EventFiredEventHandler EventFired;
    protected void OnEventFired()
    {
        if(EventFired != null)
        {
            EventFired();
        }
    }
}

这是处理此问题的正确方式吗?还是有更好的方式?我不想让ObjectA知道ObjectC,只想让它知道它引发了一个事件。


1
这是一种有效的方法。 - jvanrhyn
3个回答

24

另一种方法是使用 add/remove 进行包装:

public class ObjectB
{
    ObjectC objC;

    public ObjectB()
    {
        objC = new ObjectC();
    }

    public event EventFiredEventHandler EventFired
    {
        add { this.objC.EventFired += value; }
        remove { this.objC.EventFired -= value; }
    }
}

1
如果有很多事件需要冒泡,这似乎可以节省一些时间。谢谢。 - Tester101

5
那是我做事的方式。不过,我建议您更改您的触发机制,以使其线程安全。
protected void OnEventFired()
{
    var tmpEvent = EventFired;
    if(tmpEvent != null)
    {
        tmpEvent();
    }
}

如果在空检查和触发之间EventFired变为null,这将防止它失败。

此外,遵循EventHandler模式作为事件委托的标准。

protected virtual void OnEventFired(EventArgs e)
{
    var tmpEvent = EventFired;
    if(tmpEvent != null)
    {
        tmpEvent(this, EventArgs.e);
    }
}

我之前对线程安全模式的理解是错误的,这里是完整的线程安全事件模式。

/// <summary>
/// Delegate backing the SomeEvent event.
/// </summary>
SomeEventHandler someEvent;

/// <summary>
/// Lock for SomeEvent delegate access.
/// </summary>
readonly object someEventLock = new object();

/// <summary>
/// Description for the event
/// </summary>
public event SomeEventHandler SomeEvent
{
    add
    {
        lock (someEventLock)
        {
            someEvent += value;
        }
    }
    remove
    {
        lock (someEventLock)
        {
            someEvent -= value;
        }
    }
}

/// <summary>
/// Raises the SomeEvent event
/// </summary>
protected virtual OnSomeEvent(EventArgs e)
{
    SomeEventHandler handler;
    lock (someEventLock)
    {
        handler = someEvent;
    }
    if (handler != null)
    {
        handler (this, e);
    }
}

@Mike Cheel,正如我在帖子中所说的,如果事件在空值检查和实际触发之间被取消订阅,将会抛出异常(我认为是NullReference)。通过将事件分配给一个单独的变量,即使在此函数执行期间取消订阅,该事件仍将最后一次触发。 - Scott Chamberlain
通常的签名不是 protected virtual void OnMyEvent(MyEventArgs e) 吗? - Greg
@Scott Chamberlain:线程安全的东西似乎需要额外的工作,我能不能只用try/catch包装一下,忽略任何异常?我的意思是,如果没有人在听,那么事件是否触发真的很重要吗? - Tester101
参考赋值是原子性的,不是吗?为什么要在它周围放置锁? - Greg
@BFree 那也是我最初的想法(请看我删除的代码),但是我找不到任何在处理线程安全时不涉及 += 和 -= 锁定的例子。 - Scott Chamberlain
显示剩余9条评论

1

正如其他答案所述,这是实现它的方法。

但你可以超越它!我刚刚在上面实现了一个好的数据结构,它可能会给你带来新的体验。

想要自动事件冒泡吗?你可以使用反射来实现它。我的方法是定义一个接口/基类,声明一个事件(或一组事件)。然后,基类的无参构造函数将迭代其它属性/字段,并自动注册成员事件以进行事件传播。

设计上有一些限制,但如果你有一个深层次的结构和/或许多(结构化的)事件,那么没有任何额外的代码就可以设置好所有内容,这可能会很不错。

一个初始的基类可能是:

class BaseObject {
    public BaseObject() {
        FieldInfo[] fInfos = this.GetType().GetFields(...);

        foreach (FieldInfo fInfo in fInfos) {
            object fInfoValue = fInfo.GetValue(this, null);
            if (fInfoValue is BaseObject) {
                BaseObject bMemberObject = (BaseObject)fInfoValue;

                bMemberObject.MyEvent += new EventHandler(delegate() {
                    if (this.MyEvent != null)
                        MyEvent();
                });
            }
    }

    public event MyEvent = null;

}

当然,如已建议的那样,请遵循事件委托 delegate(object sender, EventArgs args)(我已使用更简单的事件以便清晰明了)。 自然地,暗示着你的类 ABC 直接派生自 BaseObject
请注意,可以实现任何逻辑以绑定结构化事件(您可以使用名称和/或其他反射属性进行嵌套事件注册)。

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