DCSIMG
ObservableDictionary<TKey, TValue> (C#) - Shimmy on .NET

ObservableDictionary<TKey, TValue> (C#)

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));
    }
  }
}   
Published Sunday, December 26, 2010 1:28 AM by Shimmy

Comments

# re: ObservableDictionary<TKey, TValue> (C#)

Monday, December 27, 2010 7:21 AM by Oleg

Why not to use BindingList?

# re: ObservableDictionary<TKey, TValue> (C#)

Monday, December 27, 2010 11:03 AM by Shimmy

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.

# re: ObservableDictionary<TKey, TValue> (C#)

Monday, December 27, 2010 9:00 PM by Oleg

Is observablecollection part of Framework?msdn.microsoft.com/.../ms668604.aspx

# re: ObservableDictionary<TKey, TValue> (C#)

Tuesday, December 28, 2010 12:07 AM by Shimmy

Of course, it's part of the BCL.

# re: ObservableDictionary<TKey, TValue> (C#)

Friday, January 14, 2011 11:04 AM by Horst

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.

# re: ObservableDictionary<TKey, TValue> (C#)

Sunday, January 16, 2011 2:14 AM by Shimmy

@Horst, thanks for your comment.

You're definitely right. I've edited my post.

# re: ObservableDictionary<TKey, TValue> (C#)

Monday, January 17, 2011 5:07 AM by Horst

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

# re: ObservableDictionary<TKey, TValue> (C#)

Thursday, March 31, 2011 11:13 AM by Shahar

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?

# re: ObservableDictionary<TKey, TValue> (C#)

Friday, April 01, 2011 1:30 AM by Shimmy

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<string, int> { }

XAML

<Window>

 <Window.Tag>

   <local:MyDictionary>        

     <sys:Int32 x:Key="key0">0</sys:Int32>        

     <sys:Int32 x:Key="key1">111</sys:Int32>        

     <sys:Int32 x:Key="key2">222</sys:Int32>    

   </local:MyDictionary />      

 </Window.Tag>

</Window>

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

HTH

# re: ObservableDictionary<TKey, TValue> (C#)

Wednesday, April 06, 2011 4:47 PM by Shahar

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.

# re: ObservableDictionary<TKey, TValue> (C#)

Wednesday, April 06, 2011 6:19 PM by Shimmy

@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.

# re: ObservableDictionary<TKey, TValue> (C#)

Thursday, April 07, 2011 9:56 AM by Shmulik

Hi Shimmy,

The problem Shahar is describing happens when a CollectionView is bound to the observable dictionary, and is described in social.msdn.microsoft.com/.../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.

# re: ObservableDictionary<TKey, TValue> (C#)

Monday, June 13, 2011 1:30 PM by Ricardo Peres

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.

# re: ObservableDictionary<TKey, TValue> (C#)

Tuesday, June 21, 2011 3:20 AM by Shimmy

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<TKey>) 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!

# re: ObservableDictionary<TKey, TValue> (C#)

Tuesday, June 21, 2011 3:56 AM by Shimmy

@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!

# .NET ObservableDictionary - Programmers Goodies

Tuesday, August 09, 2011 4:00 AM by .NET ObservableDictionary - Programmers Goodies

Pingback from  .NET ObservableDictionary - Programmers Goodies

# re: ObservableDictionary<TKey, TValue> (C#)

Friday, September 16, 2011 12:10 PM by Guillaume

Hi,

Thanks for sharing.

Unfortunately, your implementation of ObservableDictionary isn't complete, the Values and Keys collections should also be Observable.

# re: ObservableDictionary<TKey, TValue> (C#)

Friday, September 16, 2011 12:15 PM by Mc_Topaz

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.

# ObservableDictionary per WPF

Monday, November 21, 2011 8:14 PM by Around and About .NET World

ObservableDictionary per WPF

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems