.Net Observabledictionary

.NET ObservableDictionary

I'd suggest that you implement the IDictionary<TKey, TValue> instead of inheriting from Dictionary<TKey, TValue>. Since you have to use new rather than override it's possible that the methods are simply being called on the base class rather than your class. I'd be tempted to use a Dictionary<TKey, TValue> internally to do the actual storing of data.

In fact I found this:
http://blogs.microsoft.co.il/blogs/shimmy/archive/2010/12/26/observabledictionary-lt-tkey-tvalue-gt-c.aspx

ObservableDictionary not properly implementating INotifyCollectionChanged

After reproducing this issue and doing my own tests, I've found the problem.

Looking at the call stack, the exception is actually coming from CollectionView, which is what ItemsControl uses internally to monitor its ItemsSource. This is why the exception only occures when the ObservableDictionary is bound to.

I compared the implementation of ObservableDictionary above to the .NET source code for ObservableCollection. This link takes you to the method in the source that gets called when an item is replaced. You'll notice that they use an overload of the NotifyCollectionChangedEventArgs constructor which includes index. But the ObservableDictionary implementation uses a different constructor which does not.

Switching the constructor to the one being used by ObservableCollection prevents this exception.

So, insead of:

private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}

Use this:

private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, Dictionary.ToList().IndexOf(newItem)));
}

Remove() was throwing Collection Remove event must specify item position. exception because the called overload of CollectionChanged was also missing an index parameter.

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

TValue value;
if (!Dictionary.TryGetValue(key, out value)) { return false; }
var removeditem = new KeyValuePair<TKey, TValue>(key, value);
var removedindex = Dictionary.ToList().IndexOf(removeditem);
var removed = Dictionary.Remove(key);

if (removed)
OnCollectionChanged(NotifyCollectionChangedAction.Remove, removeditem, removedindex);

return removed;
}

And the new overload called in this case:

private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem, int removedindex)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem, removedindex));
}

I've left a note on the GitHub page for the original code (the one you linked to) informing them of this error so they can fix it- or at least so that future users are aware of it.

ObservableDictionary for c#

Similar data structure, to bind to Dictionary type collection

http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/

It provides a new Data structure ObservableDictionary and fires PropertyChanged in case of any change to underlying Dictionary.

General Observable Dictionary Class for DataBinding/WPF C#

If you really want to make an ObservableDictionary, I'd suggest creating a class that implements both IDictionary and INotifyCollectionChanged. You can always use a Dictionary internally to implement the methods of IDictionary so that you won't have to reimplement that yourself.

Since you have full knowledge of when the internal Dictionary changes, you can use that knowledge to implement INotifyCollectionChanged.

Alternative to ObservableDictionary that focuses on performance

It's very simple to write your own that does not cause a rebinding each time an add / update or delete is done. We wrote our own because of this very reason. Essentially what we do is to disable the notification that a change has been made until all objects in the collection have been processed, and then we generate the notification. It looks something like this. As you can see, we use MvvmLight as our MVVM framework library. This will improve performance tremendously.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using FS.Mes.Client.Mvvm.MvvmTools.MvvmLight;

namespace FS.Mes.Client.Mvvm.MvvmTools
{
/// <summary>
/// Our implementation of ObservableCollection. This fixes one significant limitation in the .NET Framework implementation. When items
/// are added or removed from an observable collection, the OnCollectionChanged event is fired for each item. Depending on how the collection
/// is bound, this can cause significant performance issues. This implementation gets around this by suppressing the notification until all
/// items are added or removed. This implementation is also thread safe. Operations against this collection are always done on the thread that
/// owns the collection.
/// </summary>
/// <typeparam name="T">Collection type</typeparam>
public class FsObservableCollection<T> : ObservableCollection<T>
{
#region [Constructor]
/// <summary>
/// Constructor
/// </summary>
public FsObservableCollection()
{
DispatcherHelper.Initialize();
}
#endregion

#region [Public Properties]
/// <summary>
/// Gets or sets a property that determines if we are delaying notifications on updates.
/// </summary>
public bool DelayOnCollectionChangedNotification { get; set; }
#endregion

/// <summary>
/// Add a range of IEnumerable items to the observable collection and optionally delay notification until the operation is complete.
/// </summary>
/// <param name="items"></param>
/// <param name="delayCollectionChangedNotification">Value indicating whether delay notification will be turned on/off</param>
public void AddRange(IEnumerable<T> items, bool delayCollectionChangedNotification = true)
{
if (items == null)
throw new ArgumentNullException("items");

DoDispatchedAction(() =>
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;

// Do we have any items to add?
if (items.Any())
{
try
{
foreach (var item in items)
this.Add(item);
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
});
}

/// <summary>
/// Clear the items in the ObservableCollection and optionally delay notification until the operation is complete.
/// </summary>
public void ClearItems(bool delayCollectionChangedNotification = true)
{
// Do we have anything to remove?
if (!this.Any())
return;

DoDispatchedAction(() =>
{
try
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;

this.Clear();
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
});
}

/// <summary>
/// Override the virtual OnCollectionChanged() method on the ObservableCollection class.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
DoDispatchedAction(() =>
{
if (!DelayOnCollectionChangedNotification)
base.OnCollectionChanged(e);
});
}

/// <summary>
/// Makes sure 'action' is executed on the thread that owns the object. Otherwise, things will go boom.
/// </summary>
///<param name="action">The action which should be executed</param>
private static void DoDispatchedAction(Action action)
{
DispatcherHelper.CheckInvokeOnUI(action);
}
}
}

How to reference a struct's type parameters in a generic C# class?

Well, you have 2 generic parameters, let them be TKey and TValue:

public class MyObservableDictionary<TKey, TValue> 

Your class implements two interfaces:

  1. "I want the ... collection to be a dictionary" - IDictionary<TKey, TValue>
  2. "I would like to create my own implementation of INotifyCollectionChanged"

Add them:

public class MyObservableDictionary<TKey, TValue> 
: IDictionary<TKey, TValue>,
INotifyCollectionChanged

Finally, if I've understood you right, you want to restrict both TKey and TValue to be struct only; you can do it with a help of where:

 public class MyObservableDictionary<TKey, TValue> 
: IDictionary<TKey, TValue>,
INotifyCollectionChanged
where TKey : struct
where TValue : struct {
//TODO: implementation here
}

Can't serialize my `ObservableDictionaryTKey,TValue` class

This has less to do with your class but with the data you did store in your collection. It seems that in your collection you have stored a ViewObject which internally does contain an EnumerableCollectionView object.

When you serialize data you must be sure what parts of your object graph you do want to serialize. Just putting objects in your collection could cause half of you application data sent over the wire or to disc. There is a good reason why DataContractSerializer was invented.

You should know before the serialize call what data you are going to serialize. Otherwise it could happen that e.g. in a client server application you are trying to deserialize types which are located in assemblies that do exist only on the server.



Related Topics



Leave a reply



Submit