.NET ObservableDictionary

前端 未结 6 1258
感情败类
感情败类 2020-11-27 03:07

I have written the following class which implements(or tries to!) a dictionary with notifications:

public partial class ObservableDictionary

        
6条回答
  •  清酒与你
    2020-11-27 03:43

    As Ignatio and Matthew pointed out in this answer, only raising the Reset collection change notification is incorrect, and not very useful if the caller needs to know what actually changed. Fortunately it is easily corrected. Note this version uses Send instead of Post as Nathan mentioned in the earlier answer, because WPF is sensitive to reporting back the correct index upon removal, and getting it wrong yields this confusing exception. (Caveat emptor: I'm still not completely convinced the reported index will be fully reliable if there are many overlapping changes, especially given that Dictionaries should be treated as un-ordered.)

    //--------------------------------------------------------------------------
    // 
    //  Copyright (c) Microsoft Corporation.  All rights reserved. 
    // 
    //  File: ObservableConcurrentDictionary.cs
    //
    //--------------------------------------------------------------------------
    
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Threading;
    using System.Diagnostics;
    
    namespace System.Collections.Concurrent
    {
        /// 
        /// Provides a thread-safe dictionary for use with data binding.
        /// 
        /// Specifies the type of the keys in this collection.
        /// Specifies the type of the values in this collection.
        [DebuggerDisplay("Count={Count}")]
        public class ObservableConcurrentDictionary :
            ICollection>, IDictionary,
            INotifyCollectionChanged, INotifyPropertyChanged
        {
            private readonly SynchronizationContext _context;
            private readonly ConcurrentDictionary _dictionary;
    
            /// 
            /// Initializes an instance of the ObservableConcurrentDictionary class.
            /// 
            public ObservableConcurrentDictionary()
            {
                _context = AsyncOperationManager.SynchronizationContext;
                _dictionary = new ConcurrentDictionary();
            }
    
            /// Event raised when the collection changes.
            public event NotifyCollectionChangedEventHandler CollectionChanged;
    
            /// Event raised when a property on the collection changes.
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// 
            /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
            /// 
            private void NotifyObserversOfChange()
            {
                var collectionHandler = CollectionChanged;
                var propertyHandler = PropertyChanged;
                if (collectionHandler != null || propertyHandler != null)
                {
                    _context.Send(s =>
                    {
                        collectionHandler?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Count"));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Keys"));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Values"));
                    }, null);
                }
            }
    
            /// 
            /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
            /// 
            /// Add or Update action
            /// The item involved with the change
            private void NotifyObserversOfChange(NotifyCollectionChangedAction actionType, object changedItem)
            {
                var collectionHandler = CollectionChanged;
                var propertyHandler = PropertyChanged;
                if (collectionHandler != null || propertyHandler != null)
                {
                    _context.Send(s =>
                    {
                        collectionHandler?.Invoke(this, new NotifyCollectionChangedEventArgs(actionType, changedItem));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Count"));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Keys"));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Values"));
                    }, null);
                }
            }
    
            /// 
            /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
            /// 
            /// Remove action or optionally an Add action
            /// The item in question
            /// The position of the item in the collection
            private void NotifyObserversOfChange(NotifyCollectionChangedAction actionType, object item, int index)
            {
                var collectionHandler = CollectionChanged;
                var propertyHandler = PropertyChanged;
                if (collectionHandler != null || propertyHandler != null)
                {
                    _context.Send(s =>
                    {
                        collectionHandler?.Invoke(this, new NotifyCollectionChangedEventArgs(actionType, item, index));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Count"));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Keys"));
                        propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Values"));
                    }, null);
                }
            }
    
            /// Attempts to add an item to the dictionary, notifying observers of any changes.
            /// The item to be added.
            /// Whether the add was successful.
            private bool TryAddWithNotification(KeyValuePair item)
                => TryAddWithNotification(item.Key, item.Value);
    
            /// Attempts to add an item to the dictionary, notifying observers of any changes.
            /// The key of the item to be added.
            /// The value of the item to be added.
            /// Whether the add was successful.
            private bool TryAddWithNotification(TKey key, TValue value)
            {
                bool result = _dictionary.TryAdd(key, value);
                int index = IndexOf(key);
                if (result) NotifyObserversOfChange(NotifyCollectionChangedAction.Add, value, index);
                return result;
            }
    
            /// Attempts to remove an item from the dictionary, notifying observers of any changes.
            /// The key of the item to be removed.
            /// The value of the item removed.
            /// Whether the removal was successful.
            private bool TryRemoveWithNotification(TKey key, out TValue value)
            {
                int index = IndexOf(key);
                bool result = _dictionary.TryRemove(key, out value);
                if (result) NotifyObserversOfChange(NotifyCollectionChangedAction.Remove, value, index);
                return result;
            }
    
            /// Attempts to add or update an item in the dictionary, notifying observers of any changes.
            /// The key of the item to be updated.
            /// The new value to set for the item.
            /// Whether the update was successful.
            private void UpdateWithNotification(TKey key, TValue value)
            {
                _dictionary[key] = value;
                NotifyObserversOfChange(NotifyCollectionChangedAction.Replace, value);
            }
    
            /// 
            /// WPF requires that the reported index for Add/Remove events are correct/reliable. With a dictionary there
            /// is no choice but to brute-force search through the key list. Ugly.
            /// 
            private int IndexOf(TKey key)
            {
                var keys = _dictionary.Keys;
                int index = -1;
                foreach(TKey k in keys)
                {
                    index++;
                    if (k.Equals(key)) return index;
                }
                return -1;
            }
    
            // ICollection> Members
    
    
            void ICollection>.Add(KeyValuePair item)
                => TryAddWithNotification(item);
    
            void ICollection>.Clear()
            {
                _dictionary.Clear();
                NotifyObserversOfChange();
            }
    
            bool ICollection>.Contains(KeyValuePair item)
                => ((ICollection>)_dictionary).Contains(item);
    
            void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
                => ((ICollection>)_dictionary).CopyTo(array, arrayIndex);
    
            int ICollection>.Count
            {
                get => _dictionary.Count;
            }
    
            bool ICollection>.IsReadOnly
            {
                get => ((ICollection>)_dictionary).IsReadOnly;
            }
    
            bool ICollection>.Remove(KeyValuePair item)
                => TryRemoveWithNotification(item.Key, out TValue temp);
    
    
            // IEnumerable> Members
    
    
            IEnumerator> IEnumerable>.GetEnumerator()
                => _dictionary.GetEnumerator();
    
            IEnumerator IEnumerable.GetEnumerator()
                => _dictionary.GetEnumerator();
    
    
            // IDictionary Members
    
    
            public void Add(TKey key, TValue value)
                => TryAddWithNotification(key, value);
    
            public bool ContainsKey(TKey key)
                => _dictionary.ContainsKey(key);
    
            public ICollection Keys
            {
                get { return _dictionary.Keys; }
            }
    
            public bool Remove(TKey key)
                => TryRemoveWithNotification(key, out TValue temp);
    
            public bool TryGetValue(TKey key, out TValue value)
                => _dictionary.TryGetValue(key, out value);
    
            public ICollection Values
            {
                get => _dictionary.Values;
            }
    
            public TValue this[TKey key]
            {
                get => _dictionary[key];
                set => UpdateWithNotification(key, value);
            }
        }
    }
    

提交回复
热议问题