You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
6.7 KiB
195 lines
6.7 KiB
#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<T> : Collection<T>, 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<TrackingThreadLocal<int>> _entryCheck; |
|
|
|
// This field is disposable and will not be disposed either |
|
private readonly StructNeedle<ReentryBlockage> _reentryBlockage; |
|
|
|
public ObservableCollection() |
|
: base(new List<T>()) |
|
{ |
|
_entryCheck = new TrackingThreadLocal<int>(() => 0); |
|
_reentryBlockage = new ReentryBlockage(() => _entryCheck.Value.Value--); |
|
} |
|
|
|
public ObservableCollection(IEnumerable<T> collection) |
|
: base(new List<T>(collection)) |
|
{ |
|
_entryCheck = new TrackingThreadLocal<int>(() => 0); |
|
} |
|
|
|
public ObservableCollection(List<T> list) |
|
: base(new List<T>(list)) |
|
{ |
|
_entryCheck = new TrackingThreadLocal<int>(() => 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<bool> 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 |