ObservableDictionary<TKey, TValue> (C#)

26/12/2010

Few weeks ago I posted an ObservableDictionary(Of TKey, TValue) in VB.NET, today I had some time to translate it to C# (not tested, just translated, kindly notice me for any error).

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

namespace System.Collections.ObjectModel
{
  public class ObservableDictionary<TKeyTValue> : IDictionary<TKey, TValue>, INotifyCollectionChangedINotifyPropertyChanged
  {
    private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";

    private IDictionary<TKey, TValue> _Dictionary;
    protected IDictionary<TKey, TValue> Dictionary
    {
      get { return _Dictionary; }
    }

    #region Constructors
    public ObservableDictionary()
    {
      _Dictionary = new Dictionary<TKey, TValue>();
    }
    public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
    {
      _Dictionary = new Dictionary<TKey, TValue>(dictionary);
    }
    public ObservableDictionary(IEqualityComparer<TKey> comparer)
    {
      _Dictionary = new Dictionary<TKey, TValue>(comparer);
    }
    public ObservableDictionary(int capacity)
    {
      _Dictionary = new Dictionary<TKey, TValue>(capacity);
    }
    public ObservableDictionary(IDictionary<TKey, TValue> dictionaryIEqualityComparer<TKey> comparer)
    {
      _Dictionary = new Dictionary<TKey, TValue>(dictionarycomparer);
    }
    public ObservableDictionary(int capacityIEqualityComparer<TKey> comparer)
    {
      _Dictionary = new Dictionary<TKey, TValue>(capacitycomparer);
    }
    #endregion

    #region IDictionary<TKey,TValue> Members

    public void Add(TKey key, TValue value)
    {
      Insert(keyvaluetrue);
    }

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

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

    public bool Remove(TKey key)
    {
      if (key == nullthrow new ArgumentNullException("key");

      TValue value;
      Dictionary.TryGetValue(keyout value);
      var removed = Dictionary.Remove(key);
      if (removed)
        //OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
        OnCollectionChanged();

      return removed;
    }

    public bool TryGetValue(TKey keyout TValue value)
    {
      return Dictionary.TryGetValue(keyout value);
    }

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

    public TValue this[TKey key]
    {
      get
      {
        return Dictionary[key];
      }
      set
      {
        Insert(keyvaluefalse);
      }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    public void Add(KeyValuePair<TKey, TValue> item)
    {
      Insert(item.Keyitem.Valuetrue);
    }

    public void Clear()
    {
      if (Dictionary.Count > 0)
      {
        Dictionary.Clear();
        OnCollectionChanged();
      }
    }

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

    public void CopyTo(KeyValuePair<TKey, TValue>[] arrayint arrayIndex)
    {
      Dictionary.CopyTo(arrayarrayIndex);
    }

    public int Count
    {
      get { return Dictionary.Count; }
    }

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

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

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

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

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      return ((IEnumerable)Dictionary).GetEnumerator();
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    public void AddRange(IDictionary<TKey, TValue> items)
    {
      if (items == nullthrow new ArgumentNullException("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.");
          else
            foreach (var item in itemsDictionary.Add(item);
        }
        else
          _Dictionary = new Dictionary<TKey, TValue>(items);

        OnCollectionChanged(NotifyCollectionChangedAction.Additems.ToArray());
      }                                                     
    }

    private void Insert(TKey key, TValue valuebool add)
    {
      if (key == nullthrow new ArgumentNullException("key");

      TValue item;
      if (Dictionary.TryGetValue(keyout item))
      {
        if (addthrow new ArgumentException("An item with the same key has already been added.");
        if (Equals(itemvalue)) return;
Dictionary[key] = value;

        OnCollectionChanged(NotifyCollectionChangedAction.Replacenew KeyValuePair<TKey, TValue>(keyvalue), new KeyValuePair<TKey, TValue>(keyitem));
      }
      else
{
Dictionary[key] = value;

        OnCollectionChanged(NotifyCollectionChangedAction.Addnew KeyValuePair<TKey, TValue>(keyvalue));
      }
}

    private void OnPropertyChanged()
    {
      OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
      if (PropertyChanged != nullPropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
    }

    private void OnCollectionChanged()
    {
      OnPropertyChanged();
      if (CollectionChanged != nullCollectionChanged(thisnew NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    private void OnCollectionChanged(NotifyCollectionChangedAction actionKeyValuePair<TKey, TValue> changedItem)
    {
      OnPropertyChanged();
      if (CollectionChanged != nullCollectionChanged(thisnew NotifyCollectionChangedEventArgs(actionchangedItem));
    }

    private void OnCollectionChanged(NotifyCollectionChangedAction actionKeyValuePair<TKey, TValue> newItemKeyValuePair<TKey, TValue> oldItem)
    {
      OnPropertyChanged();
      if (CollectionChanged != nullCollectionChanged(thisnew NotifyCollectionChangedEventArgs(actionnewItemoldItem));
    }

    private void OnCollectionChanged(NotifyCollectionChangedAction actionIList newItems)
    {
      OnPropertyChanged();
      if (CollectionChanged != nullCollectionChanged(thisnew NotifyCollectionChangedEventArgs(actionnewItems));
    }
  }
}   

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

21 comments

  1. Oleg27/12/2010 ב 07:21

    Why not to use BindingList?

    Reply
  2. Shimmy27/12/2010 ב 11:03

    BindingList is not meant to serve WPF anyway, WPF is expecting an INotifyPropertyChanged or INotifyCollectionChanged item and will update the UI according to events raised from these two.

    Reply
  3. Shimmy28/12/2010 ב 00:07

    Of course, it’s part of the BCL.

    Reply
  4. Horst14/01/2011 ב 11:04

    Shimmy,

    In the Insert method, you fire the CollectionChanged event before you change the dictionary, which is not only unintuitive  - I can’t really say ‘wrong’, as MS’ specification is not very, well, specific and just says the INotifyCollectionChanged.CollectionChanged event “Occurs when the collection changes.” – as the name ‘…Changed’ indicates it has already been changed, but it is also inconsistent with the rest of the operations, where the dictionary is changed first.

    Reply
  5. Shimmy16/01/2011 ב 02:14

    @Horst, thanks for your comment.
    You’re definitely right. I’ve edited my post.

    Reply
  6. Horst17/01/2011 ב 05:07

    btw. my last comment also applies to the vb version of the insert method which is still unchanged

    Reply
  7. Shahar31/03/2011 ב 11:13

    Thanks for the source code.
    Can you please provide an example with binding in xaml, so we will know how changes in the dictionary can be automatically be reflected in the UI?

    Reply
  8. Shimmy01/04/2011 ב 01:30

    The Dictionary is observable, and since it’s implements INotifyCollectionChanged and INotifyPropertyChanged, it’s ready for binding.
    Here is an example of binding to a dictionary in XAML:

    C#
    public class MyDictionary : ObservableDictionary { }

    XAML




    0
    111
    222

    Here is a better example by Pete Brown: http://10rem.net/blog/2010/03/08/binding-to-a-dictionary-in-wpf-and-silverlight

    HTH

    Reply
  9. Shahar06/04/2011 ב 16:47

    Hi,

    Thanks for the update.

    I am now facing another problem. When trying to remove an item from the dictionary, it throws the following exception:

    Collection Remove event must specify item position.

    I created an instance of the ObservableDictionary with a key that is of type long and a value which is an instance of a class that I wrote. I added several pairs of key-value to the dictionary.

    I then called the “Remove” method of the ObservableDictionary and passed it a valid key, but it still throws this exception.

    Any idea?

    Thanks,

    Shahar.

    Reply
  10. Shimmy06/04/2011 ב 18:19

    @Horst:
    I don’t remember updating it, but the Insert method on the VB version is equivalent to this one.

    @Shahar:
    I suspect it’s the NotifyCollectionChangedEventArgs’ constructor that doesn’t accept a KeyValuePair as it’s parameter.

    Please tell me more about the exception you receive.

    Reply
  11. Shmulik07/04/2011 ב 09:56

    Hi Shimmy,
    The problem Shahar is describing happens when a CollectionView is bound to the observable dictionary, and is described in http://social.msdn.microsoft.com/forums/en-US/wpf/thread/af80921b-8bf0-4763-bd30-1e01dfc6f132

    I saw you added there a comment with a link to your codeplex project, but I don’t think it is solved there.

    As Dr. WPF explains, the bound view expect to know the
    index of the removed item in order to efficiently remove it.

    Reply
  12. Ricardo Peres13/06/2011 ב 13:30

    On setter public TValue this[TKey key], you can’t be sure you’re updating, because the same syntax can be used for adding items to a dictionary.
    Also, you don’t implement ISerializable, which involves creating an additional constructor and creating the _Dictionary field dynamically, passing it the two parameters for the serialization and IDeserializationCallback.

    Reply
  13. Shimmy21/06/2011 ב 03:20

    Hello Shahar and Shmulik.
    I unfortunately didn’t have too much time to mess with this issue.
    From a brief test I made I figured out that our problem is since dictionaries are not index-based but rather key-based (on hash etc.).

    So as a temporary dirty workaround I replaced the OnCollectionChanged in the Remove method so it should now perform a complete NotifyCollectionChangedAction.Reset on every remove.
    I’m sure the performance impact is significant.

    A RemoveRange(IEnumerable) might also be a good idea (to allow bulk removes, this shoot really boost performance when dynamically removing a bunch of items i.e. after a filter).

    I’ll follow up comments and will be able to hear about any ideas and embed them in my post.
    Or if I’ll have some spare time will drill into it.

    Thank for sharing your thoughts!

    Reply
  14. Shimmy21/06/2011 ב 03:56

    @Richardo.
    Thanks.

    1) It should behave exactly as a normal dictionary. As you can see, it will throw an exception if trying to add an existing key – unlike replacing.

    2) I didn’t see an urgent need to implement ISerializable for this dictionary, it’s rather meant to be used in XAML pages for representation matters – for serialization, other resources should be used.

    Thanks for your comment!

    Reply
  15. Guillaume16/09/2011 ב 12:10

    Hi,
    Thanks for sharing.
    Unfortunately, your implementation of ObservableDictionary isn’t complete, the Values and Keys collections should also be Observable.

    Reply
  16. Mc_Topaz16/09/2011 ב 12:15

    I’m encounter a bug when trying to clear my dictionary.

    When OnPropertyChanged() method is executed from the Clear() method, it calls the OnPropertyChanged(IndexerName) method. In this method the PropertyChanged is not null and calls externally the “public TValue this[TKey key]” property. Here in the get-block it tries to return each key. In some way the program remembers the keys I just removed with the Clear() method. This cause a KeyNotFoundException to be thrown for each key.

    To get around this I just did “if(Dictionary.Count != 0) {}” around the call to onPropertyChanged(IndexerName) method.
    I have only stumbled over this bug in my application. Not in any other application.

    Reply
  17. Peter06/07/2012 ב 17:35

    Every time I try to update the value of an entry, I get an Exception at the second-to-last OnCollectionChanged() method. What’s wrong?

    Reply
  18. Shimmy07/07/2012 ב 21:47

    @Peter, why won’t you put an breakpoint somewhere before the exception is thrown and track the problem yourself, I’m currently not exactly into this project at all. I’d love to fix the code above if you manage to find the problem.

    Reply
  19. Wayne Lo21/07/2012 ב 02:11

    If I bind ListBox to the ObservableDictionary values, I will lose the binding if I clear the ObservableDictionary items. Any suggestion? Thanks

    Reply
  20. Quinton21/11/2012 ב 06:30

    Thanks, this is exactly what I needed. Really surprised that this isn’t a part of the core framework yet.

    Reply