Here is a compilation of some solutions which I made. The idea of collection changed invokation taken from first answer.
Also seems that "Reset" operation should be synchronous with main thread otherwise strange things happen to CollectionView and CollectionViewSource.
I think that's because on "Reset" handler tries to read the collection contents immediately and they should be already in place.
If you do "Reset" async and than immediately add some items also async than newly added items might be added twice.
public interface IObservableList<T> : IList<T>, INotifyCollectionChanged
{
}
public class ObservableList<T> : IObservableList<T>
{
private IList<T> collection = new List<T>();
public event NotifyCollectionChangedEventHandler CollectionChanged;
private ReaderWriterLock sync = new ReaderWriterLock();
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (CollectionChanged == null)
return;
foreach (NotifyCollectionChangedEventHandler handler in CollectionChanged.GetInvocationList())
{
// If the subscriber is a DispatcherObject and different thread.
var dispatcherObject = handler.Target as DispatcherObject;
if (dispatcherObject != null && !dispatcherObject.CheckAccess())
{
if ( args.Action == NotifyCollectionChangedAction.Reset )
dispatcherObject.Dispatcher.Invoke
(DispatcherPriority.DataBind, handler, this, args);
else
// Invoke handler in the target dispatcher's thread...
// asynchronously for better responsiveness.
dispatcherObject.Dispatcher.BeginInvoke
(DispatcherPriority.DataBind, handler, this, args);
}
else
{
// Execute handler as is.
handler(this, args);
}
}
}
public ObservableList()
{
}
public void Add(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
collection.Add(item);
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item));
}
finally
{
sync.ReleaseWriterLock();
}
}
public void Clear()
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
collection.Clear();
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset));
}
finally
{
sync.ReleaseWriterLock();
}
}
public bool Contains(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
try
{
var result = collection.Contains(item);
return result;
}
finally
{
sync.ReleaseReaderLock();
}
}
public void CopyTo(T[] array, int arrayIndex)
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
collection.CopyTo(array, arrayIndex);
}
finally
{
sync.ReleaseWriterLock();
}
}
public int Count
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
try
{
return collection.Count;
}
finally
{
sync.ReleaseReaderLock();
}
}
}
public bool IsReadOnly
{
get { return collection.IsReadOnly; }
}
public bool Remove(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
var index = collection.IndexOf(item);
if (index == -1)
return false;
var result = collection.Remove(item);
if (result)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
return result;
}
finally
{
sync.ReleaseWriterLock();
}
}
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return collection.GetEnumerator();
}
public int IndexOf(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
try
{
var result = collection.IndexOf(item);
return result;
}
finally
{
sync.ReleaseReaderLock();
}
}
public void Insert(int index, T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
collection.Insert(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
finally
{
sync.ReleaseWriterLock();
}
}
public void RemoveAt(int index)
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
if (collection.Count == 0 || collection.Count <= index)
return;
var item = collection[index];
collection.RemoveAt(index);
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item, index));
}
finally
{
sync.ReleaseWriterLock();
}
}
public T this[int index]
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
try
{
var result = collection[index];
return result;
}
finally
{
sync.ReleaseReaderLock();
}
}
set
{
sync.AcquireWriterLock(Timeout.Infinite);
try
{
if (collection.Count == 0 || collection.Count <= index)
return;
var item = collection[index];
collection[index] = value;
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace, value, item, index));
}
finally
{
sync.ReleaseWriterLock();
}
}
}
}