#if NET20 || !NET_4_6 using INotifyCollectionChanged = System.Collections.Specialized.INotifyCollectionChanged; using NotifyCollectionChangedEventHandler = System.Collections.Specialized.NotifyCollectionChangedEventHandler; using NotifyCollectionChangedEventArgs = System.Collections.Specialized.NotifyCollectionChangedEventArgs; using NotifyCollectionChangedAction = System.Collections.Specialized.NotifyCollectionChangedAction; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using LinqInternal.Threading; using LinqInternal.Threading.Needles; namespace System.Collections.ObjectModel { public class ObservableCollection : Collection, INotifyCollectionChanged, INotifyPropertyChanged { // Using TrackingThreadLocal instead of NoTrackingThreadLocal or ThreadLocal to avoid not managed resources // This field is disposable and will not be disposed private readonly StructNeedle> _entryCheck; // This field is disposable and will not be disposed either private readonly StructNeedle _reentryBlockage; public ObservableCollection() : base(new List()) { _entryCheck = new TrackingThreadLocal(() => 0); _reentryBlockage = new ReentryBlockage(() => _entryCheck.Value.Value--); } public ObservableCollection(IEnumerable collection) : base(new List(collection)) { _entryCheck = new TrackingThreadLocal(() => 0); } public ObservableCollection(List list) : base(new List(list)) { _entryCheck = new TrackingThreadLocal(() => 0); } public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; public void Move(int oldIndex, int newIndex) { MoveItem(oldIndex, newIndex); } protected IDisposable BlockReentrancy() { _entryCheck.Value.Value++; return _reentryBlockage.Value; } protected void CheckReentrancy() { int value; if (_entryCheck.Value.TryGetValue(out value) && value > 0) { throw new InvalidOperationException("Reentry"); } } protected override void ClearItems() { CheckReentrancy(); base.ClearItems(); InvokePropertyChanged("Count"); InvokePropertyChanged("Item[]"); InvokeCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } protected override void InsertItem(int index, T item) { CheckReentrancy(); base.InsertItem(index, item); InvokePropertyChanged("Count"); InvokePropertyChanged("Item[]"); InvokeCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } protected virtual void MoveItem(int oldIndex, int newIndex) { CheckReentrancy(); // While it is tempting to use monitor here, this class is not really meant to be thread-safe // Also, let it fail var item = base[oldIndex]; base.RemoveItem(oldIndex); base.InsertItem(newIndex, item); InvokePropertyChanged("Item[]"); InvokeCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex)); } protected override void RemoveItem(int index) { CheckReentrancy(); // While it is tempting to use monitor here, this class is not really meant to be thread-safe // Also, let it fail var item = base[index]; base.RemoveItem(index); InvokePropertyChanged("Count"); InvokePropertyChanged("Item[]"); InvokeCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); } protected override void SetItem(int index, T item) { CheckReentrancy(); // While it is tempting to use monitor here, this class is not really meant to be thread-safe // Also, let it fail var oldItem = base[index]; base.SetItem(index, item); InvokePropertyChanged("Item[]"); InvokeCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem, index)); } private void InvokeCollectionChanged(NotifyCollectionChangedEventArgs eventArgs) { var collectionChanged = CollectionChanged; if (collectionChanged != null) { try { _entryCheck.Value.Value++; collectionChanged.Invoke(this, eventArgs); } finally { _entryCheck.Value.Value--; } } } private void InvokePropertyChanged(string propertyName) { var propertyChanged = PropertyChanged; if (propertyChanged != null) { try { _entryCheck.Value.Value++; propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } finally { _entryCheck.Value.Value--; } } } [Diagnostics.DebuggerNonUserCode] public sealed class ReentryBlockage : IDisposable { private readonly Action _release; public ReentryBlockage(Action release) { if (release == null) { throw new ArgumentNullException("release"); } _release = release; } public bool Dispose(Func condition) { if (condition == null) { throw new ArgumentNullException("condition"); } if (condition.Invoke()) { _release.Invoke(); return true; } return false; } public void Dispose() { Dispose(true); } private void Dispose(bool disposeManagedResources) { GC.KeepAlive(disposeManagedResources); _release.Invoke(); } } } } #endif