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;
}
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
{
_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;
}
_dictionary[key] = value;
itemsToUpdate.Add(item);
}
else
{
_dictionary[key] = value;
itemsToUpdate.Add(item);
}
}
if (itemsToUpdate.Count > 0)
{
OnCollectionChanged(NotifyCollectionChangedAction.Replace, itemsToUpdate.ToArray());
}
}
}
}