创建自定义事件 - 对象发送者或类型化发送者?

8

我查阅了档案,发现有很多关于发送方是什么以及为什么应该使用该模式的问题,但我没有看到有关自定义事件和发送方类型的内容。

假设我正在创建一个名为Subscription的自定义类,它实现了ISubscription接口,并且我有一些名为SubscriptionEventArgs的事件参数。如果Subscription有一个名为Changed的事件,那么Changed(ISubscription sender, SubscriptionEventArgs e)的事件签名有什么问题吗?

下面是一些代码以帮助理解问题:

public class SubscriptionEventArgs : EventArgs
{
    // guts of event args go here
}

public interface ISubscription
{
    event Action<ISubscription, SubscriptionEventArgs> Changed;
}

public class Subscription : ISubscription
{
    public event Action<ISubscription, SubscriptionEventArgs> Changed;

    private void OnChanged(SubscriptionEventArgs e)
    {
        if (Changed!= null)
        {
            Changed(this, e);
        }
    }
}

如果您不喜欢使用动作而是想要使用“EventHandler”,那么您可以使用自定义的通用“EventHandler”来完成相同的事情。
public delegate void EventHandler<TSender, TEventArgs>(TSender sender, TEventArgs e);

public class SubscriptionEventArgs : EventArgs
{
    // guts of event args go here
}

public interface ISubscription
{
    event EventHandler<ISubscription, SubscriptionEventArgs> Changed;
}

public class Subscription : ISubscription
{
    public event EventHandler<ISubscription, SubscriptionEventArgs> Changed;

    private void OnChanged(SubscriptionEventArgs e)
    {
        if (Changed!= null)
        {
            Changed(this, e);
        }
    }
}

作为对Hans请求示例事件处理程序的回应:
public class SubscriptionCollection
{
    // what is actually holding the subscriptions is not really relevant to the question
    private List<ISubscription> _subscriptions;

    public SubscriptionCollection()
    {
        _subscriptions = new List<ISubscription>();
    }

    public void Add(ISubscription subscription)
    {
        subscription.Changed += new EventHandler<ISubscription, SubscriptionEventArgs>(Subscription_Changed);
        _subscriptions.Add(subscription);
    }

    private void Subscription_Changed(ISubscription sender, SubscriptionEventArgs e)
    {
        // Now when the subscription changed event is being handled by the collection
        // I don't have to look up the subscription in the list by some key and I don't 
        // have to cast sender to the correct type because the event handler was typed
        // correctly from the beginning.
    }
}

在列表中查找订阅似乎是微不足道的,但如果我正在处理庞大的数据集,并且新的数据量通过实时流传入应用程序,那么停下来从列表中获取引用或执行转换步骤的成本就没有意义了。他们在2.0中给我们提供了泛型以解决这个问题,所以我不明白为什么我们没有得到一个通用的事件处理程序,这使我开始质疑通用事件处理程序有什么问题。


如果添加“where TEventArgs : EventArgs”语句,我会更喜欢第二个代码块。 - Matt DeKrey
只是一个小建议,如果您使用IObservable而不是ISubscription作为接口类型,我认为您会更好地遵循设计模式的命名,并且一眼就能更清楚地看出正在发生什么。 - Matt DeKrey
1个回答

0

我其实很困惑,为什么在设计 .Net Framework v2 时,微软没有像你描述的那样提供一个带有 TSenderTEventArgs 作为两个泛型参数的 EventHandler。 (在 v1 和 v1.1 中,由于它们没有泛型,我完全理解为什么他们没有制作成千上万个额外的委托类型来处理所有可能的事件。)如果我没记错的话,您仍然可以使用通用处理程序来监听更具体的事件:

public event EventHandler<Button, MouseDownEventArgs> MouseDown;

private void ObservingMethod(object sender, EventArgs e) { }

MouseDown += new EventHandler<Button, MouseDownEventArgs>(ObservingMethod);

既然您没有将观察者暴露给可观察对象,我不认为这会成为问题;您只是防止在事件处理程序中需要进行类型检查“以防万一”。我认为这是一个很好的做法,尽管有点非标准,因为微软决定不包括它。

如我上面的评论所述,我更喜欢看到以下EventHandler的定义,这样您就可以始终使用非常通用的处理程序方法,就像我的代码示例:

public delegate void EventHandler<TSender, TEventArgs>(TSender sender, TEventArgs e)
    where TEventArgs : EventArgs;

可能是因为大多数这些事件都在基类(Control - ButtonBase - ...)中声明,因此不会将Button类作为发送方。(当时还没有协变 ;) - Stormenet
这是公平的,但即使使用最严格的对象,即使它是已知的抽象基类或接口,也将是有帮助的。例如,在MouseDown事件中调用CaptureMouse时快速访问而无需转换。而且,既然显式委托包装可行(如上所述,不使用泛型),我们仍然能够使用广义的事件处理程序。 - Matt DeKrey

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