WPF ObservableDictionary

19 阅读2分钟
 public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged where TKey : notnull
 {
     private IDictionary<TKey, TValue> _dictionary;

     public event NotifyCollectionChangedEventHandler? CollectionChanged;
     public event PropertyChangedEventHandler? PropertyChanged;

     public ObservableDictionary()
     {
         _dictionary = new Dictionary<TKey, TValue>();
     }

     public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
     {
         _dictionary = new Dictionary<TKey, TValue>(dictionary);
     }

     private TValue GetValue(TKey key)
     {
         return _dictionary[key];
     }

     protected void DoAddOrUpdate(TKey key, TValue value, bool add)
     {
         if (_dictionary.TryGetValue(key, out TValue? oldValue))
         {
             if (add)
             {
                 throw new ArgumentException("An item with the same key has already been added.");
             }

             if (Equals(oldValue, value))
             {
                 return;
             }

             // Update
             int index = GetIndexAndEntryForKey(key, out KeyValuePair<TKey, TValue> oldPair);
             _dictionary[key] = value;
             GetIndexAndEntryForKey(key, out KeyValuePair<TKey, TValue> newPair);
             OnCollectionChanged(NotifyCollectionChangedAction.Replace, newPair, oldPair, index);
         }
         else
         {
             // Add
             _dictionary[key] = value;
             int index = GetIndexAndEntryForKey(key, out KeyValuePair<TKey, TValue> pair);
             OnCollectionChanged(NotifyCollectionChangedAction.Add, pair, index);
         }
     }

     protected bool DoRemove(TKey key)
     {
         if (key == null)
         {
             throw new ArgumentNullException(nameof(key));
         }

         int index = GetIndexAndEntryForKey(key, out KeyValuePair<TKey, TValue> pair);
         if (index != -1)
         {
             var removed = _dictionary.Remove(key);
             OnCollectionChanged(NotifyCollectionChangedAction.Remove, pair, index);
             return removed;
         }
         return false;
     }

     protected int GetIndexAndEntryForKey(TKey key, out KeyValuePair<TKey, TValue> pair)
     {
         pair = default;
         int index = 0;
         foreach (var item in this)
         {
             if (item.Key.Equals(key))
             {
                 pair = item;
                 return index;
             }
             index++;
         }
         return -1;
     }

     #region 事件通知
     protected void OnPropertyChanged(string propertyName)
     {
         this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
     }

     private void OnPropertyChanged()
     {
         OnPropertyChanged(nameof(Count));
         OnPropertyChanged(nameof(Keys));
         OnPropertyChanged(nameof(Values));
         OnPropertyChanged("Item[]");
     }

     protected void OnCollectionChanged(NotifyCollectionChangedAction action)
     {
         OnPropertyChanged();
         this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
     }

     protected void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem, int index)
     {
         OnPropertyChanged();
         this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, changedItem, index));
     }

     protected void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem, int index)
     {
         OnPropertyChanged();
         this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
     }

     protected void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
     {
         OnPropertyChanged();
         this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, newItems, 0));
     }
     #endregion

     #region 接口实现
     public int Count
     {
         get { return _dictionary.Count; }
     }

     public bool IsReadOnly
     {
         get { return _dictionary.IsReadOnly; }
     }

     public ICollection<TKey> Keys
     {
         get { return _dictionary.Keys; }
     }

     public ICollection<TValue> Values
     {
         get { return _dictionary.Values; }
     }

     public TValue this[TKey key]
     {
         get { return GetValue(key); }
         set { DoAddOrUpdate(key, value, false); }
     }

     public void Add(TKey key, TValue value)
     {
         DoAddOrUpdate(key, value, true);
     }

     public void Add(KeyValuePair<TKey, TValue> item)
     {
         DoAddOrUpdate(item.Key, item.Value, true);
     }

     public bool Remove(TKey key)
     {
         return DoRemove(key);
     }

     public bool Remove(KeyValuePair<TKey, TValue> item)
     {
         return DoRemove(item.Key);
     }

     public void Clear()
     {
         if (_dictionary.Count > 0)
         {
             _dictionary.Clear();
             OnCollectionChanged(NotifyCollectionChangedAction.Reset);
         }
     }

     public bool ContainsKey(TKey key)
     {
         return _dictionary.ContainsKey(key);
     }

     public bool Contains(KeyValuePair<TKey, TValue> item)
     {
         return _dictionary.Contains(item);
     }

     public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
     {
         return _dictionary.TryGetValue(key, out value);
     }

     public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
     {
         _dictionary.CopyTo(array, arrayIndex);
     }

     public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
     {
         return _dictionary.GetEnumerator();
     }

     IEnumerator IEnumerable.GetEnumerator()
     {
         return _dictionary.GetEnumerator();
     }
     #endregion

     public void AddRange(IDictionary<TKey, TValue> items)
     {
         if (items == null)
         {
             throw new ArgumentNullException(nameof(items));
         }

         if (items.Count > 0)
         {
             if (_dictionary.Count > 0)
             {
                 if (items.Keys.Any(k => _dictionary.ContainsKey(k)))
                 {
                     throw new ArgumentException("An item with the same key has already been added.");
                 }

                 foreach (var item in items)
                 {
                     _dictionary.Add(item);
                 }
             }
             else
             {
                 _dictionary = new Dictionary<TKey, TValue>(items);
             }

             OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
         }
     }

     public void AddUpdateRange(IDictionary<TKey, TValue> items, bool add = false)
     {
         if (items == null)
         {
             throw new ArgumentNullException(nameof(items));
         }

         if (items.Count > 0)
         {
             if (add)
             {
                 if (items.Keys.Any(k => _dictionary.ContainsKey(k)))
                 {
                     throw new ArgumentException("An item with the same key has already been added.");
                 }
             }

             List<KeyValuePair<TKey, TValue>> itemsToUpdate = new();
             foreach (KeyValuePair<TKey, TValue> item in items)
             {
                 TKey key = item.Key;
                 TValue value = item.Value;
                 if (_dictionary.TryGetValue(key, out TValue? oldValue))
                 {
                     if (Equals(oldValue, value))
                     {
                         continue;
                     }
                     // Update
                     _dictionary[key] = value;
                     itemsToUpdate.Add(item);
                 }
                 else
                 {
                     // Add
                     _dictionary[key] = value;
                     itemsToUpdate.Add(item);
                 }
             }

             if (itemsToUpdate.Count > 0)
             {
                 OnCollectionChanged(NotifyCollectionChangedAction.Replace, itemsToUpdate.ToArray());
             }
         }
     }

 }