如何使用 ConcurrentDictionary、INotifyCollectionChanged、INotifyPropertyChanged 创建自定义可观察集合

发布于 2024-09-27 14:02:06 字数 10507 浏览 4 评论 0原文

我正在尝试创建一个 ObservableConcurrentDictionary。该对象将在多线程应用程序中使用,并且它的数据用于通过控件的 ItemsSource 属性填充控件。

这是我提出的实现:

public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    #region Constructors

    public ObservableConcurrentDictionary()
        : base()
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        : base(collection)
    { 

    }


    public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer)
        : base(comparer)
    { 

    }


    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
        : base(concurrencyLevel, capacity)
    { 

    }


    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(collection, comparer)
    { 

    }


    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, capacity, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, collection, comparer)
    {

    }

    #endregion

    #region Public Methods

    public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
        {
            // Update value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
        }
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Returns the value
        return value;
    }

    public void Clear()
    {
        // Clear dictionary
        base.Clear();
        // Raise event
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            value = base.GetOrAdd(key, valueFactory);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.GetOrAdd(key, valueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Return value
        return value;
    }

    public new TValue GetOrAdd(TKey key, TValue value)
    {
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            base.GetOrAdd(key, value);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            base.GetOrAdd(key, value);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Return value
        return value;
    }

    public new bool TryAdd(TKey key, TValue value)
    {
        // Stores tryAdd
        bool tryAdd;
        // If added
        if (tryAdd = base.TryAdd(key, value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        // Return tryAdd
        return tryAdd;
    }

    public new bool TryRemove(TKey key, out TValue value)
    {
        // Stores tryRemove
        bool tryRemove;
        // If removed
        if (tryRemove = base.TryRemove(key, out value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
        // Return tryAdd
        return tryRemove;
    }

    public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        // Stores tryUpdate
        bool tryUpdate;
        // If updated
        if (tryUpdate = base.TryUpdate(key, newValue, comparisonValue))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
        // Return tryUpdate
        return tryUpdate;
    }

    #endregion

    #region Private Methods

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

不幸的是,该解决方案无法按预期工作 - 事实上,它根本不起作用。关于我做错了什么有什么想法或者是否存在更好的解决方案?

请注意,我不能使用 ObservableCollection,因此我必须编写自己的 Observable 集合。

编辑: 工作版本如下。希望这可以帮助其他遇到类似问题的人。

public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    public ObservableConcurrentDictionary()
        : base()
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        : base(collection)
    { 

    }

    public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer)
        : base(comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
        : base(concurrencyLevel, capacity)
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(collection, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, capacity, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, collection, comparer)
    {

    }

    public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
        {
            // Update value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value));
        }
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Returns the value
        return value;
    }

    public new void Clear()
    {
        // Clear dictionary
        base.Clear();
        // Raise event
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            value = base.GetOrAdd(key, valueFactory);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.GetOrAdd(key, valueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Return value
        return value;
    }

    public new TValue GetOrAdd(TKey key, TValue value)
    {
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            base.GetOrAdd(key, value);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            base.GetOrAdd(key, value);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Return value
        return value;
    }

    public new bool TryAdd(TKey key, TValue value)
    {
        // Stores tryAdd
        bool tryAdd;
        // If added
        if (tryAdd = base.TryAdd(key, value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        // Return tryAdd
        return tryAdd;
    }

    public new bool TryRemove(TKey key, out TValue value)
    {
        // Stores tryRemove
        bool tryRemove;
        // If removed
        if (tryRemove = base.TryRemove(key, out value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
        // Return tryAdd
        return tryRemove;
    }

    public new bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        // Stores tryUpdate
        bool tryUpdate;
        // If updated
        if (tryUpdate = base.TryUpdate(key, newValue, comparisonValue))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue));
        // Return tryUpdate
        return tryUpdate;
    }

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public event PropertyChangedEventHandler PropertyChanged;
}

I am trying to create an ObservableConcurrentDictionary. This object will be used in a multithreaded application, and it's data is used to populate a control via the controls ItemsSource property.

This is the implementation i have come up with:

public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    #region Constructors

    public ObservableConcurrentDictionary()
        : base()
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        : base(collection)
    { 

    }


    public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer)
        : base(comparer)
    { 

    }


    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
        : base(concurrencyLevel, capacity)
    { 

    }


    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(collection, comparer)
    { 

    }


    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, capacity, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, collection, comparer)
    {

    }

    #endregion

    #region Public Methods

    public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
        {
            // Update value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
        }
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Returns the value
        return value;
    }

    public void Clear()
    {
        // Clear dictionary
        base.Clear();
        // Raise event
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            value = base.GetOrAdd(key, valueFactory);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.GetOrAdd(key, valueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Return value
        return value;
    }

    public new TValue GetOrAdd(TKey key, TValue value)
    {
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            base.GetOrAdd(key, value);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            base.GetOrAdd(key, value);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        }
        // Return value
        return value;
    }

    public new bool TryAdd(TKey key, TValue value)
    {
        // Stores tryAdd
        bool tryAdd;
        // If added
        if (tryAdd = base.TryAdd(key, value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
        // Return tryAdd
        return tryAdd;
    }

    public new bool TryRemove(TKey key, out TValue value)
    {
        // Stores tryRemove
        bool tryRemove;
        // If removed
        if (tryRemove = base.TryRemove(key, out value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
        // Return tryAdd
        return tryRemove;
    }

    public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        // Stores tryUpdate
        bool tryUpdate;
        // If updated
        if (tryUpdate = base.TryUpdate(key, newValue, comparisonValue))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
        // Return tryUpdate
        return tryUpdate;
    }

    #endregion

    #region Private Methods

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Unfortunately the solution does not work as intended - in fact, it doesn't work at all. Any ideas on what i am doing wrong or do any better solutions exist?

Please note i CAN'T USE ObservableCollection, hence i have to write my own Observable collection.

EDIT:
The working version is below. Hope this helps someone else with a similar problem.

public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    public ObservableConcurrentDictionary()
        : base()
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        : base(collection)
    { 

    }

    public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer)
        : base(comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
        : base(concurrencyLevel, capacity)
    { 

    }

    public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(collection, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, capacity, comparer)
    { 

    }

    public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
        : base(concurrencyLevel, collection, comparer)
    {

    }

    public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
        {
            // Update value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value));
        }
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Returns the value
        return value;
    }

    public new void Clear()
    {
        // Clear dictionary
        base.Clear();
        // Raise event
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
    {
        // Stores the value
        TValue value;
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            value = base.GetOrAdd(key, valueFactory);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            value = base.GetOrAdd(key, valueFactory);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Return value
        return value;
    }

    public new TValue GetOrAdd(TKey key, TValue value)
    {
        // If key exists
        if (base.ContainsKey(key))
            // Get value
            base.GetOrAdd(key, value);
        // Else if key does not exist
        else
        {
            // Add value and raise event
            base.GetOrAdd(key, value);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        }
        // Return value
        return value;
    }

    public new bool TryAdd(TKey key, TValue value)
    {
        // Stores tryAdd
        bool tryAdd;
        // If added
        if (tryAdd = base.TryAdd(key, value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
        // Return tryAdd
        return tryAdd;
    }

    public new bool TryRemove(TKey key, out TValue value)
    {
        // Stores tryRemove
        bool tryRemove;
        // If removed
        if (tryRemove = base.TryRemove(key, out value))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
        // Return tryAdd
        return tryRemove;
    }

    public new bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        // Stores tryUpdate
        bool tryUpdate;
        // If updated
        if (tryUpdate = base.TryUpdate(key, newValue, comparisonValue))
            // Raise event
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue));
        // Return tryUpdate
        return tryUpdate;
    }

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public event PropertyChangedEventHandler PropertyChanged;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

〗斷ホ乔殘χμё〖 2024-10-04 14:02:06

我只能猜测,快速浏览您的代码,而无需您的任何解释。
我认为在 NotifyCollectionChangedEventArgs 上设置 Action 就足够了。还有 NewItemsOldItems 属性,告诉订阅者哪些项目发生了更改。

另请注意,虽然这些是集合,但许多 WPF 组件仅支持通过 DataBinding 一次更改单个项目。

Quickly going through your code without any eplanation from your side I can only guess.
I dont think setting Action on NotifyCollectionChangedEventArgs is enough. There are also NewItems, OldItems properties, that tell subscriber what items got changed.

Also note that, while those are collections, many WPF components support only single item change at a time through DataBinding.

你另情深 2024-10-04 14:02:06

我开发了 ObservableConcurrentDictionnary 的紧凑版本,请评论/建议...

...其中 TValue : Object {使用您的类而不是 Object...}

Qurlet

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Collections
{
    public class ObservableConcurrentDictionary<TValue> : ConcurrentDictionary<Int32, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
        where TValue : Object , new()
    {
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs changeAction)
        {
            var eh = CollectionChanged;
            if (eh == null) return;

            eh(this, changeAction);

            OnPropertyChanged();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged()
        {
            var eh = PropertyChanged;
            if (eh == null) return;

            // All properties : Keys, Values, Count, IsEmpty
            eh(this, new PropertyChangedEventArgs(null));
        }

        #region Ctors
        public ObservableConcurrentDictionary()
            : base()
        {

        }

        public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<Int32, TValue>> collection)
            : base(collection)
        {

        }

        public ObservableConcurrentDictionary(IEqualityComparer<Int32> comparer)
            : base(comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
            : base(concurrencyLevel, capacity)
        {

        }

        public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<Int32, TValue>> collection, IEqualityComparer<Int32> comparer)
            : base(collection, comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<Int32> comparer)
            : base(concurrencyLevel, capacity, comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<Int32, TValue>> collection, IEqualityComparer<Int32> comparer)
            : base(concurrencyLevel, collection, comparer)
        {

        }
        #endregion

        public new void Clear()
        {
            // Clear dictionary
            base.Clear();
            // Raise event
            OnCollectionChanged(changeAction: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public new TValue AddOrUpdate(Int32 key, Func<Int32, TValue> addValueFactory,
            Func<Int32, TValue, TValue> updateValueFactory)
        {
            bool isUpdated = false;
            TValue oldValue = default(TValue);

            TValue value = base.AddOrUpdate(key, addValueFactory, (k, v) =>
            {
                isUpdated = true;
                oldValue = v;
                return updateValueFactory(k, v);
            });

            if (isUpdated) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return value;
        }

        public new TValue AddOrUpdate(Int32 key, TValue addValue, Func<Int32, TValue, TValue> updateValueFactory)
        {
            bool isUpdated = false;
            TValue oldValue = default(TValue);

            TValue value = base.AddOrUpdate(key, addValue, (k, v) =>
            {
                isUpdated = true;
                oldValue = v;
                return updateValueFactory(k, v);
            });

            if (isUpdated) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return value;
        }

        public new TValue GetOrAdd(Int32 key, Func<Int32, TValue> addValueFactory)
        {
            bool isAdded = false;

            TValue value = base.GetOrAdd(key, k =>
            {
                isAdded = true;
                return addValueFactory(k);
            });

            if (isAdded) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));

            return value;
        }

        public new TValue GetOrAdd(Int32 key, TValue value)
        {
            return GetOrAdd(key, k => value);
        }

        public new bool TryAdd(Int32 key, TValue value)
        {
            bool tryAdd = base.TryAdd(key, value);

            if (tryAdd) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));

            return tryAdd;
        }

        public new bool TryRemove(Int32 key, out TValue value)
        {
            // Stores tryRemove
            bool tryRemove = base.TryRemove(key, out value);

            // If removed raise event
            if (tryRemove) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));

            return tryRemove;
        }

        public new bool TryUpdate(Int32 key, TValue newValue, TValue comparisonValue)
        {
            // Stores tryUpdate
            bool tryUpdate = base.TryUpdate(key, newValue, comparisonValue);

            if (tryUpdate) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue, comparisonValue));

            return tryUpdate;
        }

    }
}

I developed a tight version of an ObservableConcurrentDictionnary, please comment/suggest...

... where TValue : Object {use your class instead of Object...}

Qurlet

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Collections
{
    public class ObservableConcurrentDictionary<TValue> : ConcurrentDictionary<Int32, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
        where TValue : Object , new()
    {
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs changeAction)
        {
            var eh = CollectionChanged;
            if (eh == null) return;

            eh(this, changeAction);

            OnPropertyChanged();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged()
        {
            var eh = PropertyChanged;
            if (eh == null) return;

            // All properties : Keys, Values, Count, IsEmpty
            eh(this, new PropertyChangedEventArgs(null));
        }

        #region Ctors
        public ObservableConcurrentDictionary()
            : base()
        {

        }

        public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<Int32, TValue>> collection)
            : base(collection)
        {

        }

        public ObservableConcurrentDictionary(IEqualityComparer<Int32> comparer)
            : base(comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, int capacity)
            : base(concurrencyLevel, capacity)
        {

        }

        public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<Int32, TValue>> collection, IEqualityComparer<Int32> comparer)
            : base(collection, comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<Int32> comparer)
            : base(concurrencyLevel, capacity, comparer)
        {

        }

        public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<Int32, TValue>> collection, IEqualityComparer<Int32> comparer)
            : base(concurrencyLevel, collection, comparer)
        {

        }
        #endregion

        public new void Clear()
        {
            // Clear dictionary
            base.Clear();
            // Raise event
            OnCollectionChanged(changeAction: new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public new TValue AddOrUpdate(Int32 key, Func<Int32, TValue> addValueFactory,
            Func<Int32, TValue, TValue> updateValueFactory)
        {
            bool isUpdated = false;
            TValue oldValue = default(TValue);

            TValue value = base.AddOrUpdate(key, addValueFactory, (k, v) =>
            {
                isUpdated = true;
                oldValue = v;
                return updateValueFactory(k, v);
            });

            if (isUpdated) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return value;
        }

        public new TValue AddOrUpdate(Int32 key, TValue addValue, Func<Int32, TValue, TValue> updateValueFactory)
        {
            bool isUpdated = false;
            TValue oldValue = default(TValue);

            TValue value = base.AddOrUpdate(key, addValue, (k, v) =>
            {
                isUpdated = true;
                oldValue = v;
                return updateValueFactory(k, v);
            });

            if (isUpdated) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return value;
        }

        public new TValue GetOrAdd(Int32 key, Func<Int32, TValue> addValueFactory)
        {
            bool isAdded = false;

            TValue value = base.GetOrAdd(key, k =>
            {
                isAdded = true;
                return addValueFactory(k);
            });

            if (isAdded) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));

            return value;
        }

        public new TValue GetOrAdd(Int32 key, TValue value)
        {
            return GetOrAdd(key, k => value);
        }

        public new bool TryAdd(Int32 key, TValue value)
        {
            bool tryAdd = base.TryAdd(key, value);

            if (tryAdd) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));

            return tryAdd;
        }

        public new bool TryRemove(Int32 key, out TValue value)
        {
            // Stores tryRemove
            bool tryRemove = base.TryRemove(key, out value);

            // If removed raise event
            if (tryRemove) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));

            return tryRemove;
        }

        public new bool TryUpdate(Int32 key, TValue newValue, TValue comparisonValue)
        {
            // Stores tryUpdate
            bool tryUpdate = base.TryUpdate(key, newValue, comparisonValue);

            if (tryUpdate) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue, comparisonValue));

            return tryUpdate;
        }

    }
}
寂寞花火° 2024-10-04 14:02:06

对于那些仍然感兴趣的人,下面是从 @MStack 和 @c0D3l0g1c 的工作开始的完整实现,并添加文档、INotifyPropertyChanged 的实现以及 >= .NET 4.7 中的新方法。

/// <summary>
/// Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently and provides
/// notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
   private const string IndexerName = "Item[]";

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// default concurrency level, has the default initial capacity, and uses the default comparer for the key type.
   /// </summary>
   /// <remarks>
   /// The default concurrency level is equal to the number of CPUs. The higher the concurrency level is, the more concurrent write
   /// operations can take place without interference and blocking. Higher concurrency level values also cause operations that require
   /// all locks (for example, table resizing, ToArray and Count) to become more expensive. The default capacity (DEFAULT_CAPACITY),
   /// which represents the initial number of buckets, is a trade-off between the size of a very small dictionary and the number of
   /// resizes when constructing a large dictionary. Also, the capacity should not be divisible by a small prime number. The default
   /// capacity is 31.
   /// </remarks>
   public ObservableConcurrentDictionary() : base()
   {
   }

   /// <summary>Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains
   /// elements copied from the specified IEnumerable<T>, has the default concurrency level, has the default initial capacity, and uses
   /// the default comparer for the key type.</summary> <param name="collection">The <see cref="IEnumerable{T}" /> whose elements are
   /// copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}" />.</param>
   public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : base(collection)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// default concurrency level and capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// specified concurrency level and capacity, and uses the default comparer for the key type.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="capacity">
   /// The initial number of elements that the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> can contain.
   /// </param>
   public ObservableConcurrentDictionary(int concurrencyLevel, int capacity) : base(concurrencyLevel, capacity)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains elements copied
   /// from the specified <see cref="IEnumerable{T}"/> has the default concurrency level, has the default initial capacity, and uses
   /// the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="collection">
   /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
      : base(collection, comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// specified concurrency level, has the specified initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="capacity">
   /// The initial number of elements that the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> can contain.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
      : base(concurrencyLevel, capacity, comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains elements copied
   /// from the specified IEnumerable, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="collection">
   /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
      : base(concurrencyLevel, collection, comparer)
   {
   }

   /// <summary>Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.</summary>
   public event NotifyCollectionChangedEventHandler CollectionChanged;

   /// <summary>Occurs when a property value changes.</summary>
   public event PropertyChangedEventHandler PropertyChanged;

   /// <summary>
   /// Uses the specified functions to add a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the
   /// key does not already exist, or to update a key/value pair in the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if
   /// the key already exists.
   /// </summary>
   /// <param name="key">The key to be added or whose value should be updated</param>
   /// <param name="addValueFactory">The function used to generate a value for an absent key</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value
   /// </param>
   /// <returns>
   /// The new value for the key. This will be either be the result of <paramref name="addValueFactory"/> (if the key was absent) or
   /// the result of <paramref name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key does not already exist, or
   /// updates a key/value pair in the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function if
   /// the key already exists.
   /// </summary>
   /// <param name="key">The key to be added or whose value should be updated</param>
   /// <param name="addValue">The value to be added for an absent key</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value
   /// </param>
   /// <returns>
   /// The new value for the key. This will be either be <paramref name="addValue"/> (if the key was absent) or the result of <paramref
   /// name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValue, updateValueFactory);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }

#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462)
   /// <summary>
   /// Uses the specified functions and argument to add a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey,
   /// TValue}"/> if the key does not already exist, or to update a key/value pair in the <see
   /// cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key already exists.
   /// </summary>
   /// <typeparam name="TArg">The type of an argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</typeparam>
   /// <param name="key">The key to be added or whose value should be updated.</param>
   /// <param name="addValueFactory">The function used to generate a value for an absent key.</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value.
   /// </param>
   /// <param name="factoryArgument">An argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</param>
   /// <returns>
   /// The new value for the key. This will be either be the result of <paramref name="addValueFactory"/> (if the key was absent) or
   /// the result of <paramref name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate<TArg>(TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValueFactory, updateValueFactory, factoryArgument);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }
#endif

   /// <summary>Removes all keys and values from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.</summary>
   public new void Clear()
   {
      base.Clear();
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function if the
   /// key does not already exist. Returns the new value, or the existing value if the key exists.
   /// </summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="valueFactory">The function used to generate a value for the key.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.GetOrAdd(key, valueFactory);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key does not already exist.
   /// Returns the new value, or the existing value if the key exists.
   /// </summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="value">The value to be added, if the key does not already exist.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd(TKey key, TValue value)
   {
      var add = !ContainsKey(key);
      base.GetOrAdd(key, value);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }

#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462)
   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function and an
   /// argument if the key does not already exist, or returns the existing value if the key exists.
   /// </summary>
   /// <typeparam name="TArg">The type of an argument to pass into <paramref name="valueFactory"/>.</typeparam>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="valueFactory">The function used to generate a value for the key.</param>
   /// <param name="factoryArgument">An argument value to pass into <paramref name="valueFactory"/>.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument)
   {
      var add = !ContainsKey(key);
      var value = base.GetOrAdd(key, valueFactory, factoryArgument);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }
#endif

   /// <summary>Attempts to add the specified key and value to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.</summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="value">The value of the element to add. The value can be <see langword="null"/> for reference types.</param>
   /// <returns>
   /// <see langword="true"/> if the key/value pair was added to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>
   /// successfully; <see langword="false"/> if the key already exists.
   /// </returns>
   public new bool TryAdd(TKey key, TValue value)
   {
      if (base.TryAdd(key, value))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
         return true;
      }
      return false;
   }

   /// <summary>
   /// Attempts to remove and return the value that has the specified key from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </summary>
   /// <param name="key">The key of the element to remove and return.</param>
   /// <param name="value">
   /// When this method returns, contains the object removed from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>, or
   /// the default value of the <typeparamref name="TValue"/> type if <paramref name="key"/> does not exist.
   /// </param>
   /// <returns><see langword="true"/> if the object was removed successfully; otherwise, <see langword="false"/>.</returns>
   public new bool TryRemove(TKey key, out TValue value)
   {
      if (base.TryRemove(key, out value))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
         return true;
      }
      return false;
   }

   /// <summary>
   /// Updates the value associated with <paramref name="key"/> to <paramref name="newValue"/> if the existing value with <paramref
   /// name="key"/> is equal to <paramref name="comparisonValue"/>.
   /// </summary>
   /// <param name="key">The key of the value that is compared with <paramref name="comparisonValue"/> and possibly replaced.</param>
   /// <param name="newValue">
   /// The value that replaces the value of the element that has the specified <paramref name="key"/> if the comparison results in equality.
   /// </param>
   /// <param name="comparisonValue">The value that is compared with the value of the element that has the specified <paramref name="key"/>.</param>
   /// <returns>
   /// <see langword="true"/> if the value with <paramref name="key"/> was equal to <paramref name="comparisonValue"/> and was replaced
   /// with <paramref name="newValue"/>; otherwise, <see langword="false"/>.
   /// </returns>
   public new bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
   {
      if (base.TryUpdate(key, newValue, comparisonValue))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue));
         return true;
      }
      return false;
   }

   /// <summary>Raises the <see cref="CollectionChanged"/> event.</summary>
   /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
   private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      if (e.Action != NotifyCollectionChangedAction.Replace) OnPropertyChanged(nameof(Count));
      OnPropertyChanged(IndexerName);
      CollectionChanged?.Invoke(this, e);
   }

   /// <summary>Raises the <see cref="PropertyChanged" /> event.</summary>
   /// <param name="propertyName">Name of the property that has changed.</param>
   private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

For those still interested, below is a full implementation starting from the work of @MStack and @c0D3l0g1c and adding documentation, implementation of INotifyPropertyChanged, and new methods in >= .NET 4.7.

/// <summary>
/// Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently and provides
/// notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
public sealed class ObservableConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
   private const string IndexerName = "Item[]";

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// default concurrency level, has the default initial capacity, and uses the default comparer for the key type.
   /// </summary>
   /// <remarks>
   /// The default concurrency level is equal to the number of CPUs. The higher the concurrency level is, the more concurrent write
   /// operations can take place without interference and blocking. Higher concurrency level values also cause operations that require
   /// all locks (for example, table resizing, ToArray and Count) to become more expensive. The default capacity (DEFAULT_CAPACITY),
   /// which represents the initial number of buckets, is a trade-off between the size of a very small dictionary and the number of
   /// resizes when constructing a large dictionary. Also, the capacity should not be divisible by a small prime number. The default
   /// capacity is 31.
   /// </remarks>
   public ObservableConcurrentDictionary() : base()
   {
   }

   /// <summary>Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains
   /// elements copied from the specified IEnumerable<T>, has the default concurrency level, has the default initial capacity, and uses
   /// the default comparer for the key type.</summary> <param name="collection">The <see cref="IEnumerable{T}" /> whose elements are
   /// copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}" />.</param>
   public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : base(collection)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// default concurrency level and capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// specified concurrency level and capacity, and uses the default comparer for the key type.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="capacity">
   /// The initial number of elements that the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> can contain.
   /// </param>
   public ObservableConcurrentDictionary(int concurrencyLevel, int capacity) : base(concurrencyLevel, capacity)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains elements copied
   /// from the specified <see cref="IEnumerable{T}"/> has the default concurrency level, has the default initial capacity, and uses
   /// the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="collection">
   /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
      : base(collection, comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that is empty, has the
   /// specified concurrency level, has the specified initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="capacity">
   /// The initial number of elements that the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> can contain.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
      : base(concurrencyLevel, capacity, comparer)
   {
   }

   /// <summary>
   /// Initializes a new instance of the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> class that contains elements copied
   /// from the specified IEnumerable, and uses the specified <see cref="IEqualityComparer{T}"/>.
   /// </summary>
   /// <param name="concurrencyLevel">
   /// The estimated number of threads that will update the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> concurrently.
   /// </param>
   /// <param name="collection">
   /// The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </param>
   /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing keys.</param>
   public ObservableConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
      : base(concurrencyLevel, collection, comparer)
   {
   }

   /// <summary>Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.</summary>
   public event NotifyCollectionChangedEventHandler CollectionChanged;

   /// <summary>Occurs when a property value changes.</summary>
   public event PropertyChangedEventHandler PropertyChanged;

   /// <summary>
   /// Uses the specified functions to add a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the
   /// key does not already exist, or to update a key/value pair in the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if
   /// the key already exists.
   /// </summary>
   /// <param name="key">The key to be added or whose value should be updated</param>
   /// <param name="addValueFactory">The function used to generate a value for an absent key</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value
   /// </param>
   /// <returns>
   /// The new value for the key. This will be either be the result of <paramref name="addValueFactory"/> (if the key was absent) or
   /// the result of <paramref name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValueFactory, updateValueFactory);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key does not already exist, or
   /// updates a key/value pair in the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function if
   /// the key already exists.
   /// </summary>
   /// <param name="key">The key to be added or whose value should be updated</param>
   /// <param name="addValue">The value to be added for an absent key</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value
   /// </param>
   /// <returns>
   /// The new value for the key. This will be either be <paramref name="addValue"/> (if the key was absent) or the result of <paramref
   /// name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValue, updateValueFactory);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }

#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462)
   /// <summary>
   /// Uses the specified functions and argument to add a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey,
   /// TValue}"/> if the key does not already exist, or to update a key/value pair in the <see
   /// cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key already exists.
   /// </summary>
   /// <typeparam name="TArg">The type of an argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</typeparam>
   /// <param name="key">The key to be added or whose value should be updated.</param>
   /// <param name="addValueFactory">The function used to generate a value for an absent key.</param>
   /// <param name="updateValueFactory">
   /// The function used to generate a new value for an existing key based on the key's existing value.
   /// </param>
   /// <param name="factoryArgument">An argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</param>
   /// <returns>
   /// The new value for the key. This will be either be the result of <paramref name="addValueFactory"/> (if the key was absent) or
   /// the result of <paramref name="updateValueFactory"/> (if the key was present).
   /// </returns>
   public new TValue AddOrUpdate<TArg>(TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument)
   {
      var add = !ContainsKey(key);
      var value = base.AddOrUpdate(key, addValueFactory, updateValueFactory, factoryArgument);
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(add ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace, value));
      return value;
   }
#endif

   /// <summary>Removes all keys and values from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.</summary>
   public new void Clear()
   {
      base.Clear();
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function if the
   /// key does not already exist. Returns the new value, or the existing value if the key exists.
   /// </summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="valueFactory">The function used to generate a value for the key.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
   {
      var add = !ContainsKey(key);
      var value = base.GetOrAdd(key, valueFactory);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }

   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> if the key does not already exist.
   /// Returns the new value, or the existing value if the key exists.
   /// </summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="value">The value to be added, if the key does not already exist.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd(TKey key, TValue value)
   {
      var add = !ContainsKey(key);
      base.GetOrAdd(key, value);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }

#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462)
   /// <summary>
   /// Adds a key/value pair to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/> by using the specified function and an
   /// argument if the key does not already exist, or returns the existing value if the key exists.
   /// </summary>
   /// <typeparam name="TArg">The type of an argument to pass into <paramref name="valueFactory"/>.</typeparam>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="valueFactory">The function used to generate a value for the key.</param>
   /// <param name="factoryArgument">An argument value to pass into <paramref name="valueFactory"/>.</param>
   /// <returns>
   /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new
   /// value if the key was not in the dictionary.
   /// </returns>
   public new TValue GetOrAdd<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument)
   {
      var add = !ContainsKey(key);
      var value = base.GetOrAdd(key, valueFactory, factoryArgument);
      if (add) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
      return value;
   }
#endif

   /// <summary>Attempts to add the specified key and value to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.</summary>
   /// <param name="key">The key of the element to add.</param>
   /// <param name="value">The value of the element to add. The value can be <see langword="null"/> for reference types.</param>
   /// <returns>
   /// <see langword="true"/> if the key/value pair was added to the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>
   /// successfully; <see langword="false"/> if the key already exists.
   /// </returns>
   public new bool TryAdd(TKey key, TValue value)
   {
      if (base.TryAdd(key, value))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
         return true;
      }
      return false;
   }

   /// <summary>
   /// Attempts to remove and return the value that has the specified key from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>.
   /// </summary>
   /// <param name="key">The key of the element to remove and return.</param>
   /// <param name="value">
   /// When this method returns, contains the object removed from the <see cref="ObservableConcurrentDictionary{TKey, TValue}"/>, or
   /// the default value of the <typeparamref name="TValue"/> type if <paramref name="key"/> does not exist.
   /// </param>
   /// <returns><see langword="true"/> if the object was removed successfully; otherwise, <see langword="false"/>.</returns>
   public new bool TryRemove(TKey key, out TValue value)
   {
      if (base.TryRemove(key, out value))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
         return true;
      }
      return false;
   }

   /// <summary>
   /// Updates the value associated with <paramref name="key"/> to <paramref name="newValue"/> if the existing value with <paramref
   /// name="key"/> is equal to <paramref name="comparisonValue"/>.
   /// </summary>
   /// <param name="key">The key of the value that is compared with <paramref name="comparisonValue"/> and possibly replaced.</param>
   /// <param name="newValue">
   /// The value that replaces the value of the element that has the specified <paramref name="key"/> if the comparison results in equality.
   /// </param>
   /// <param name="comparisonValue">The value that is compared with the value of the element that has the specified <paramref name="key"/>.</param>
   /// <returns>
   /// <see langword="true"/> if the value with <paramref name="key"/> was equal to <paramref name="comparisonValue"/> and was replaced
   /// with <paramref name="newValue"/>; otherwise, <see langword="false"/>.
   /// </returns>
   public new bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
   {
      if (base.TryUpdate(key, newValue, comparisonValue))
      {
         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue));
         return true;
      }
      return false;
   }

   /// <summary>Raises the <see cref="CollectionChanged"/> event.</summary>
   /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
   private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      if (e.Action != NotifyCollectionChangedAction.Replace) OnPropertyChanged(nameof(Count));
      OnPropertyChanged(IndexerName);
      CollectionChanged?.Invoke(this, e);
   }

   /// <summary>Raises the <see cref="PropertyChanged" /> event.</summary>
   /// <param name="propertyName">Name of the property that has changed.</param>
   private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文