如何在C#中引用一个事件

12
我有以下这个类,其中有一个名为LengthChanged的公共事件:
class Dimension
{
    public int Length
    {
        get
        {
            return this.length;
        }
        set
        {
            if (this.length != value)
            {
                this.length = value;
                this.OnLengthChanged ();
            }
    }

    protected virtual void OnLengthChanged()
    {
        var handler = this.LengthChanged;
        if (handler != null)
        {
            handler (this, System.EventArgs.Empty);
        }
    }

    public event System.EventHandler LengthChanged;

    private int length;
}

我想在一个名为Observer的方法中注册/取消注册此事件的处理程序,该方法不知道Dimension类的任何信息。我想出了两种方案,但都不是很令人满意:

  1. 定义一个带有LengthChanged事件的接口ILengthChanged,然后确保Dimension实现了ILengthChanged。然后,我必须为我定义的每个接口提供一个Observer方法的实现。这远远不够通用。我真的希望能够简单地传递一个对System.EventHandler事件的引用。

  2. Observer方法中使用System.Action<System.EventHandler>回调来注册和取消注册事件处理程序,就像这样:

    class Foo
    {
        public void Observer(System.Action<System.EventHandler> register,
                             System.Action<System.EventHandler> unregister)
        {
            register (this.MyEventHandler);

            // keep track of the unregister callback, so that we can unregister
            // our event handler later on, if needed...
        }

        private void MyEventHandler(object sender, System.EventArgs e)
        {
            ...
        }
    }

然后可以像这样调用:

Foo foo = ...;
Dimension dim = ...;

foo.Observer (x => dim.LengthChanged += x, x => dim.LengthChanged -= x);

执行后,将确实使用内部事件处理程序MyEventHandler连接LengthChanged事件。但这并不是很优雅。我希望能够写成:

Foo foo = ...;
Dimension dim = ...;

foo.Observer (dim.LengthChanged);

但我不知道如何实现。也许我在这里漏掉了一些非常明显的东西?我猜想一些 dynamic 魔法可以以某种方式解决问题,但这将不会强制进行编译时类型检查:我不希望 Observer 的使用者传递不符合 System.EventHandler 事件签名的事件引用。

3个回答

13

很不幸,没有真正的方法可以做到这一点。在.NET中,事件通常不是第一类公民,尽管F#试图在其中推广它们。

要么传递subscribe/unsubscribe委托,要么使用表示事件名称的字符串。(后者通常更短,但显然在编译时更不安全。)

这些是Reactive Extensions所采用的方法-如果有更简洁的方法,我相信他们会使用它: (


如果像Erik Meijer这样的人无法提出更清晰的解决方案,我怀疑确实没有其他方法可以做到。这很遗憾...感谢您的快速回复,Jon。 - Pierre Arnaud

4
您可以创建一个自定义访问器。
public event EventHandler NewEvent
{
    add { Dimension.LengthChanged += value; }
    remove { Dimension.LengthChanged -= value; }
}

请参阅文档。 (链接)

2

事件不应该被传递到另一个方法中。 但是,你可以传递委托到另一个方法中。也许,你只需要一个简单的公共委托而不是事件。

如果你将你的事件更改为这样

public System.EventHandler LengthChanged; 

您可以像这样将 LengthChanged 直接传递给 Observer。
Foo foo = ...;
Dimension dim = ...;
foo.Observer (dim.LengthChanged); 

谢谢您的建议。不,我不能传递委托而不是事件;事件可能会实现addremove访问器,并且必须被定义为完整的C# event - Pierre Arnaud
@Pierre 我明白了,那么我会声明一个委托包装类。该类的构造函数接受一个委托。该类具有虚拟的AddHandler()和虚拟的RemoveHandler()方法。它还有一个Invoke()方法来调用委托。为了使其看起来更优雅,您可以定义运算符+=、-=、+和-,调用您的AddHandler和RemoveHandler。将您的Dimension的LengthChanged事件替换为此包装类。如果您需要“添加”和“删除”setter,则只需覆盖该包装类的AddHandler和RemoveHandler方法。这是我的建议。我也做过类似的事情。 - Harvey Kwok
不错的解决方案...我得好好考虑一下。为这个不错的技巧点赞。 - Pierre Arnaud

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