C#: ObservableCollection - 为什么需要虚拟的“CollectionChanged”事件?

3
为什么ObservableCollection中的CollectionChanged事件是虚拟的?我们有虚拟的OnCollectionChanged方法,应该足以覆盖事件调用,对吗?
我没有看到任何用途,而且虚拟事件很糟糕。不恰当地使用虚拟事件可能会带来很多逻辑问题,但是无论如何,虚拟事件即使在框架中也存在。
这只是糟糕的设计还是有人在实际中使用它?

4
ObservableCollection 旨在作为一个基类,不将事件设为 virtual 将是糟糕的设计。你认为相反情况会怎样呢? - Jon
Jon,假设有人在派生类中覆盖了CollectionChanged事件,并保留了OnCollectionChanged方法不变。这会使得每个订阅(甚至是WPF引擎)都进入派生类的CollectionChanged事件,从而破坏了OnCollectionChanged方法,因为它触发了空的基础事件。 - Arsen Mkrtchyan
2
因此,派生类的作者如果不注意自己在做什么,就可能会写出错误。这让人感到惊讶吗?这是派生作者的错还是基础作者的错?object.GetHashCode()不应该是虚拟的吗?因为你可以重写它并忘记重写Equals - Jon
是的,但如果没有强烈的理由,我宁愿不使用虚拟事件。在这里,我看不到任何理由,因为可以通过重写OnCollectionChanged方法来实现相同的逻辑。 - Arsen Mkrtchyan
2
我们是在讨论您的偏好还是设计决策?如果事件不是“虚拟”的,那么派生类不能更改其基础表示,并且如果它们决定要这样做,它们也不能检查订阅者。 “到目前为止我还没有需要它”不是一个论点。 - Jon
事件和处理程序不是同一个东西。 - paparazzo
2个回答

4
我们可以就基类和设计进行辩论,但这里提供一个非直接/学术的答案,更多是一个例子。我个人认为,我可以扩展ObservableCollection并重写OnCollectionChanged非常棒。ObservableCollection非常啰嗦,每次添加/删除项目时,它会向UI线程发送大量的属性更改消息并拖慢它的速度(例如在数据网格中,它需要更新其中的每个绑定)。因此,据我所知,许多人扩展了ObservableCollection以抑制此类通知,直到他们完成添加项为止。只因为WPF控件DataGrids/ListViews等响应CollectionChanged这一点,这种方法有效。
以下是用法,在刷新数据时,我不是一个个添加项目,而是填充一个列表,然后只需一次性使用它来重置ObservableCollection,从而极大地加快了UI响应速度:
private void OnExecuteRefreshCompleted(IEnumerable<MyObject> result)
{
UiUtilities.OnUi(() => { _myObservableCollectionField.Reset(result, true);              
        });

这是我的扩展类:

public class ObservableCollectionExtended<T> : ObservableCollection<T>
{
    private bool _suppressNotification;

            //without virtual , I couldn't have done this override
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_suppressNotification)
            base.OnCollectionChanged(e);
    }

    public void Clear(bool suppressNotificationUntillComplete)
    {
        _suppressNotification = suppressNotificationUntillComplete;

        Clear();

        _suppressNotification = false;
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public void ClearItems(bool suppressNotificationUntillComplete)
    {
        _suppressNotification = suppressNotificationUntillComplete;

        base.ClearItems();

        _suppressNotification = false;
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public void AddRange(IEnumerable<T> list, bool suppressNotificationUntillComplete)
    {
        if (list == null)
            throw new ArgumentNullException("list");

        _suppressNotification = suppressNotificationUntillComplete;

        foreach (T item in list)
            Add(item);

        _suppressNotification = false;
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary>
    /// clears old items, and new ones
    /// </summary>
    /// <param name="list"></param>
    /// <param name="suppressNotificationUntillComplete"></param>
    public void Reset(IEnumerable<T> list, bool suppressNotificationUntillComplete)
    {
        if (list == null)
            throw new ArgumentNullException("list");

        _suppressNotification = suppressNotificationUntillComplete;

        Clear();

        foreach (T item in list)
            Add(item);

        _suppressNotification = false;
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

1
我担心 Denis 没有回答“为什么 CollectionChanged 事件是虚拟的”这个问题,而是回答了“为什么 OnCollectionChanged() 方法是虚拟的”这个问题。第一个问题更适合由 Jon 回答。您可能会对订户的不同处理方式感兴趣,就像我之前做过的那样。
让我们看两种不同的情况:
第一种情况: 我想触发 CollectionChanged 事件,并确保在此事件中调用的任何委托(委托列表)在出现异常时不会中断后续委托的调用。换句话说,事件由委托列表组成。如果我有10个订阅者(委托),并且第3个委托引发异常,我可能会继续调用其余的委托。标准实现会中断调用。
第二种情况: 即使某些订阅者比其他订阅者晚订阅,我可能也希望让一些订阅者优先级更高(它们更早地接收到事件)。在“添加”事件中,我可以将某些特定的订阅者移动到自定义委托列表中的较低或较高位置,然后用于触发事件。

我认为在这两种情况下最好创建新的事件,因为您正在添加不被您类的用户所期望的逻辑到CollectionChanged事件中... 这样会让他们感到困惑。 - Arsen Mkrtchyan

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