是否可以订阅替代的基本事件?

时间:2018-07-24 17:15:59

标签: c# .net

两个类:

    public class ObservableCollection<T>
    {
        public virtual event NotifyCollectionChangedEventHandler CollectionChanged;

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            CollectionChanged.Invoke(this, e);
        }
    }

    public class DerivedObservableCollection<T> : ObservableCollection<T>
    {
        private bool _SuppressNotification;

        public override event NotifyCollectionChangedEventHandler CollectionChanged;

        protected virtual void OnCollectionChangedMultiItem(
            NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
            if (handlers != null)
            {
                foreach (NotifyCollectionChangedEventHandler handler in 
                    handlers.GetInvocationList())
                {
                    if (handler.Target is CollectionView)
                        ((CollectionView)handler.Target).Refresh();
                    else
                        handler(this, e);
                }
            }
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (!_SuppressNotification)
            {
                base.OnCollectionChanged(e);//<------is it usefull?------------
                if (CollectionChanged != null)
                    CollectionChanged.Invoke(this, e);
            }
        }
    }

可以在DerivedObservableCollection实例(在DerivedObservableCollection之外)的ObservableCollection.CollectionChanged上订阅吗? 或致电:

base.OnCollectionChanged(e);

实际上,我遇到了System.Collections.ObjectModel.ObservableCollection和这个answer的问题。

1 个答案:

答案 0 :(得分:3)

在c#中,不应将事件声明为virtual。相反,当您在类中声明事件时,最佳实践是还提供引发该事件的protected virtual方法-这就是您在问题中提供的代码中的方法OnCollectionChanged。 (顺便说一句,请注意,如果没有人在听此事件,则此方法将引发异常-您应确保它不为null-因此将CollectionChanged.Invoke(this, e);更改为CollectionChanged?.Invoke(this, e);。)

这样做的原因是c#编译器无法正确处理虚拟事件,如How to: Raise Base Class Events in Derived Classes (C# Programming Guide)中所述(您可以感谢@ itsme86在问题注释中的链接):

  

注意

     

不要在基类中声明虚拟事件,而在派生类中重写它们。 C#编译器无法正确处理这些事件,并且无法确定派生事件的订阅者是否实际上将在订阅基类事件。

只能从声明它的类内部引发一个事件,这就是为什么您需要 protected virtual方法从派生类中引发它-也记录在同一页中:

  

当创建一个可用作其他类的基类的类时,应考虑以下事实:事件是一种特殊类型的委托,只能从声明它们的类中调用。派生类无法直接调用基类中声明的事件。

正确的解决方案是不覆盖事件,而仅覆盖引发事件的方法-也就是说,当您想要更改引发事件的条件时,可以通过调用base.OnCollectionChanged(e);来引发事件。

正确的代码实现如下所示:

public class ObservableCollection<T>
{
    // Note: the event is no longer virtual
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        CollectionChanged?.Invoke(this, e);
    }
}

public class DerivedObservableCollection<T> : ObservableCollection<T>
{
    private bool _SuppressNotification;

    // This line is removed:
    // public override event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChangedMultiItem(
        NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if (handlers != null)
        {
            foreach (NotifyCollectionChangedEventHandler handler in 
                handlers.GetInvocationList())
            {
                if (handler.Target is CollectionView)
                    ((CollectionView)handler.Target).Refresh();
                else
                    handler(this, e);
            }
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_SuppressNotification)
        {
            // This is where you raise the event
            base.OnCollectionChanged(e);
            // the next two lines are also removed, since you've already raised the event
            // and you can't raise it directly from the derived class anyway

            //if (CollectionChanged != null)
            //    CollectionChanged.Invoke(this, e);
        }
    }
}