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
5 years ago
|
#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
|