ObservableCollection还监视集合中元素的更改

sor*_*rke 32 c# collections

是否有一个具有以下特征的集合(BCL或其他):

如果更改集合则发送事件如果集合中的任何元素发送事件,则发送PropertyChanged事件.排序的ObservableCollection<T>位置T: INotifyPropertyChanged和集合也监视元素的变化.

我可以自己包装一个可观察的集合,并在添加/删除集合中的元素时进行事件订阅/取消订阅,但我只是想知道现有的集合是否已经这样做了?

sor*_*rke 43

快速实现自己:

public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        Unsubscribe(e.OldItems);
        Subscribe(e.NewItems);
        base.OnCollectionChanged(e);
    }

    protected override void ClearItems()
    {
        foreach(T element in this)
            element.PropertyChanged -= ContainedElementChanged;

        base.ClearItems();
    }

    private void Subscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged += ContainedElementChanged;
        }
    }

    private void Unsubscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged -= ContainedElementChanged;
        }
    }

    private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged(e);
    }
}
Run Code Online (Sandbox Code Playgroud)

承认,当实际更改的属性位于包含的元素上时,对集合触发PropertyChanged会有点混乱和误导,但这符合我的特定目的.它可以通过在ContainerElementChanged内触发的新事件进行扩展

思考?

编辑:应该注意,BCL ObservableCollection只通过显式实现公开INotifyPropertyChanged接口,因此您需要提供一个强制转换才能附加到事件,如下所示:

ObservableCollectionEx<Element> collection = new ObservableCollectionEx<Element>();
((INotifyPropertyChanged)collection).PropertyChanged += (x,y) => ReactToChange();
Run Code Online (Sandbox Code Playgroud)

EDIT2:添加了对ClearItems的处理,感谢Josh

EDIT3:为MarkC添加了正确的取消订阅,感谢Mark

EDIT4:哇,这真的像你一样学习:).KP注意到,当包含的元素发生变化时,事件是以集合作为发送者而不是元素触发的.他建议在标有new的类上声明一个PropertyChanged事件.这将有一些问题,我将尝试用下面的示例来说明:

  // work on original instance
  ObservableCollection<TestObject> col = new ObservableCollectionEx<TestObject>();
  ((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // no event raised
  test.Info = "NewValue"; //Info property changed raised

  // working on explicit instance
  ObservableCollectionEx<TestObject> col = new ObservableCollectionEx<TestObject>();
  col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // Count and Item [] property changed raised
  test.Info = "NewValue"; //no event raised
Run Code Online (Sandbox Code Playgroud)

您可以从示例中看到,"覆盖"事件具有副作用,您需要非常小心在订阅事件时使用哪种类型的变量,因为这决定了您收到的事件.

  • 但是这个课程存在问题.如果您调用Clear(),OnCollectionChanged事件将获得重置通知,您将无法访问从集合中清除的项目.这可以通过重写ClearItems并在调用base.ClearItems()之前取消订阅处理程序来缓解. (4认同)

Mar*_*eld 7

@soren.enemaerke:我会在你的回答帖子上发表评论,但我不能(我不知道为什么,也许是因为我没有很多代表点).无论如何,我只是认为我在你发布的代码中提到我不认为Unsubscribe会正常工作,因为它正在创建一个新的lambda内联然后尝试删除它的事件处理程序.

我会将添加/删除事件处理程序行更改为:

element.PropertyChanged += ContainedElementChanged;
Run Code Online (Sandbox Code Playgroud)

element.PropertyChanged -= ContainedElementChanged;
Run Code Online (Sandbox Code Playgroud)

然后将ContainedElementChanged方法签名更改为:

private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
Run Code Online (Sandbox Code Playgroud)

这将识别删除是与添加相同的处理程序,然后正确删除它.希望这有助于某人:)