使用继承事件的调用列表是个坏主意吗?

Is it a bad idea to use the invocation list of an inherited event?

我有一个 class 继承自 ObservableCollection<T>。在那个 class 中,我有一个在内部更改集合的方法,我想为此抑制 CollectionChanged 事件。

public class ContentBlockList : ObservableCollection<int> {
    public void SomeMethod() {
        var handlers = CollectionChanged.GetInvocationList();

        foreach (NotifyCollectionChangedEventHandler handler in handlers) {
            CollectionChanged -= handler;
        }

        // do stuff here

        foreach (NotifyCollectionChangedEventHandler handler in handlers) {
            CollectionChanged += handler;
        }
    }
}

直觉上这似乎应该可行,因为我正在从其包含的对象中访问该事件。不幸的是,编译器说

The event 'ObservableCollection.CollectionChanged' can only appear on the left hand side of += or -=

如果我重写 CollectionChangedOnCollectionChanged(),我可以让代码工作,实质上是用我自己的副本替换 .NET 版本。然而,不得不做这样的事情让我怀疑我忽略了一些为什么这样做首先是个坏主意的原因。感谢您对此的任何想法。

最好使用这个:

public class ContentBlockList : ObservableCollection<int>
    {
        ContentBlockList()
        {
            this.CollectionChanged += ContentBlockList_CollectionChanged;
        }

        void ContentBlockList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {

        }
    }

如果您维护自己的代码,试试这个

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ContentBlockList pp = new ContentBlockList();
        pp.CollectionChanged += pp_CollectionChanged;
        pp.CollectionChanged += pp_CollectionChanged1; 
        pp.Add(11112);

        pp.SomeMethod();
    }

    void pp_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {

    }

    void pp_CollectionChanged1(object sender, NotifyCollectionChangedEventArgs e)
    {

    }


}

public class ContentBlockList : ObservableCollection<int>
{
    public void SomeMethod()
    {
        var handlers = CollectionChanged.GetInvocationList();

        foreach (NotifyCollectionChangedEventHandler handler in handlers)
        {
            CollectionChanged -= handler;
        }

        // do stuff here

        foreach (NotifyCollectionChangedEventHandler handler in handlers)
        {
            CollectionChanged += handler;
        }
    }

    public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
}

据我了解,您需要中断 CollectionChanged 的触发才能安静地完成一些工作。所以你可以创建像 __FireCollectionChanged 这样的布尔字段,然后覆盖 OnCollectionChanged() 来做:

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    if (__FireCollectionChanged)
        base.OnCollectionChanged(e);
}

然后您可以控制该布尔字段是否触发事件。

并回答实际问题:不能直接使用调用列表,因为事件不是委托类型字段。对于 subscribe/unsubscribe 行为,它基本上只是两种方法 addremove。底层委托字段是在幕后创建的,您通常不想使用它。

由于取消订阅和重新订阅一个事件是一个相对(不是很痛苦,但我不知道可能有多少订阅者)缓慢的过程,我建议您考虑覆盖 OnCollectionChangedOnPropertyChanged 基础方法 ObservableCollection.

所以有类似的东西:

public class ContentBlockList : ObservableCollection<int>
{
    private bool internallyUpdating;

    public void SomeMethod()
    {
        this.internallyUpdating = true;

        // Do Stuff (Add to base collection)

        this.internallyUpdating = false;
        this.OnPropertyChanged(new PropertyChangedEventArgs(@"Count");
        this.OnPropertyChanged(new PropertyChangedEventArgs(@"Item[]");
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if(this.internallyUpdating)
        {
            return;
        }

        base.OnCollectionChanged(e);
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if(this.internallyUpdating)
        {
            return;
        } 

        base.OnPropertyChanged(e);
    }
}

这允许在您进行内部更新时抑制引发的事件,但这样做意味着您不必取消订阅和重新订阅事件。

正常添加到此集合时(即使用 contentBlockList.Add(1)),您将直接调用基本事件。但是,当您尝试进行内部更新时,您将抑制这些事件,直到完成为止。 我会说这在性能方面更高效,而且代码比您看到的更简洁。

最后一点,我还要说您提供的 NotifyCollectionChangedEventActionReset。您可能对集合做了相当大的更改,为了处理它,您会希望任何订阅者都必须刷新他们在集合上的外观,无论是 WPF 视图中的控件还是另一个 class 使用集合。