| File: ListProxy.cs | Web Access |
| Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls) |
#nullable disable using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Dispatching; namespace Microsoft.Maui.Controls { internal sealed class ListProxy : IReadOnlyList<object>, IListProxy, INotifyCollectionChanged { readonly IDispatcher _dispatcher; readonly ICollection _collection; readonly IList _list; readonly int _windowSize; readonly WeakNotifyCollectionChangedProxy _proxy; readonly NotifyCollectionChangedEventHandler _collectionChangedDelegate; IEnumerator _enumerator; int _enumeratorIndex; bool _finished; HashSet<int> _indexesCounted; Dictionary<int, object> _items; int _version; int _windowIndex; internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue, IDispatcher dispatcher = null) { _dispatcher = dispatcher; _windowSize = windowSize; ProxiedEnumerable = enumerable; _collection = enumerable as ICollection; if (_collection == null && enumerable is IReadOnlyCollection<object> coll) _collection = new ReadOnlyListAdapter(coll); _list = enumerable as IList; if (_list == null && enumerable is IReadOnlyList<object>) _list = new ReadOnlyListAdapter((IReadOnlyList<object>)enumerable); if (enumerable is INotifyCollectionChanged changed) { _collectionChangedDelegate = OnCollectionChanged; _proxy = new WeakNotifyCollectionChangedProxy(changed, _collectionChangedDelegate); } } ~ListProxy() => _proxy?.Unsubscribe(); public IEnumerable ProxiedEnumerable { get; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator<object> GetEnumerator() { return new ProxyEnumerator(this); } /// <summary> /// Gets whether or not the current window contains the <paramref name="item" />. /// </summary> /// <param name="item">The item to search for.</param> /// <returns><c>true</c> if the item was found in a list or the current window, <c>false</c> otherwise.</returns> public bool Contains(object item) { if (_list != null) return _list.Contains(item); EnsureWindowCreated(); if (_items != null) return _items.Values.Contains(item); return false; } /// <summary> /// Gets the index for the <paramref name="item" /> if in a list or the current window. /// </summary> /// <param name="item">The item to search for.</param> /// <returns>The index of the item if in a list or the current window, -1 otherwise.</returns> public int IndexOf(object item) { if (_list != null) return _list.IndexOf(item); EnsureWindowCreated(); if (_items != null) { foreach (KeyValuePair<int, object> kvp in _items) { if (Equals(kvp.Value, item)) return kvp.Key; } } return -1; } public event NotifyCollectionChangedEventHandler CollectionChanged; public int Count { get { if (_collection != null) return _collection.Count; EnsureWindowCreated(); if (_indexesCounted != null) return _indexesCounted.Count; return 0; } } public object this[int index] { get { object value; if (!TryGetValue(index, out value)) throw new ArgumentOutOfRangeException(nameof(index)); return value; } } public void Clear() { _version++; _finished = false; _windowIndex = 0; _enumeratorIndex = 0; if (_enumerator != null) { var dispose = _enumerator as IDisposable; if (dispose != null) dispose.Dispose(); _enumerator = null; } if (_items != null) _items.Clear(); if (_indexesCounted != null) _indexesCounted.Clear(); OnCountChanged(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public event EventHandler CountChanged; void ClearRange(int index, int clearCount) { if (_items == null) return; for (int i = index; i < index + clearCount; i++) _items.Remove(i); } bool CountIndex(int index) { if (_collection != null) return false; // A collection is used in case TryGetValue is called out of order. if (_indexesCounted == null) _indexesCounted = new HashSet<int>(); if (_indexesCounted.Contains(index)) return false; _indexesCounted.Add(index); return true; } void EnsureWindowCreated() { if (_items != null && _items.Count > 0) return; object value; TryGetValue(0, out value); } void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Action action; if (_list == null) { action = Clear; } else { action = () => { _version++; OnCollectionChanged(e); }; } CollectionSynchronizationContext sync; if (BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out sync)) { sync.Callback(ProxiedEnumerable, sync.Context, () => { e = e.WithCount(Count); _dispatcher.DispatchIfRequired(action); }, false); } else { e = e.WithCount(Count); _dispatcher.DispatchIfRequired(action); } } void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler changed = CollectionChanged; if (changed != null) changed(this, e); } void OnCountChanged() { EventHandler changed = CountChanged; if (changed != null) changed(this, EventArgs.Empty); } bool TryGetValue(int index, out object value) { value = null; CollectionSynchronizationContext syncContext; BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out syncContext); if (_list != null) { object indexedValue = null; var inRange = false; Action getFromList = () => { if (index >= _list.Count) return; indexedValue = _list[index]; inRange = true; }; if (syncContext != null) syncContext.Callback(ProxiedEnumerable, syncContext.Context, getFromList, false); else getFromList(); value = indexedValue; return inRange; } if (_collection != null && index >= _collection.Count) return false; if (_items != null) { bool found = _items.TryGetValue(index, out value); if (found || _finished) return found; } if (index >= _windowIndex + _windowSize) { int newIndex = index - _windowSize / 2; ClearRange(_windowIndex, newIndex - _windowIndex); _windowIndex = newIndex; } else if (index < _windowIndex) { int clearIndex = _windowIndex; int clearSize = _windowSize; if (clearIndex <= index + clearSize) { int diff = index + clearSize - clearIndex; clearIndex += diff + 1; clearSize -= diff; } ClearRange(clearIndex, clearSize); _windowIndex = 0; var dispose = _enumerator as IDisposable; if (dispose != null) dispose.Dispose(); _enumerator = null; _enumeratorIndex = 0; } if (_enumerator == null) _enumerator = ProxiedEnumerable.GetEnumerator(); if (_items == null) _items = new Dictionary<int, object>(); var countChanged = false; int end = _windowIndex + _windowSize; for (; _enumeratorIndex < end; _enumeratorIndex++) { var moved = false; Action move = () => { try { moved = _enumerator.MoveNext(); } catch (InvalidOperationException ioex) { throw new InvalidOperationException("You must call UpdateNonNotifyingList() after updating a list that does not implement INotifyCollectionChanged", ioex); } if (!moved) { var dispose = _enumerator as IDisposable; if (dispose != null) dispose.Dispose(); _enumerator = null; _enumeratorIndex = 0; _finished = true; } }; if (syncContext == null) move(); else syncContext.Callback(ProxiedEnumerable, syncContext.Context, move, false); if (!moved) break; if (CountIndex(_enumeratorIndex)) countChanged = true; if (_enumeratorIndex >= _windowIndex) _items.Add(_enumeratorIndex, _enumerator.Current); } if (countChanged) OnCountChanged(); return _items.TryGetValue(index, out value); } class ProxyEnumerator : IEnumerator<object> { readonly ListProxy _proxy; readonly int _version; int _index; public ProxyEnumerator(ListProxy proxy) { _proxy = proxy; _version = proxy._version; } public void Dispose() { } public bool MoveNext() { if (_proxy._version != _version) throw new InvalidOperationException(); object value; bool next = _proxy.TryGetValue(_index++, out value); if (next) Current = value; return next; } public void Reset() { _index = 0; Current = null; } public object Current { get; private set; } } #region IList bool IListProxy.TryGetValue(int index, out object value) => TryGetValue(index, out value); object IList.this[int index] { get { return this[index]; } set { throw new NotSupportedException(); } } bool IList.IsReadOnly { get { return true; } } bool IList.IsFixedSize { get { return false; } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { throw new NotSupportedException(); } } void ICollection.CopyTo(Array array, int index) { throw new NotSupportedException(); } int IList.Add(object item) { throw new NotSupportedException(); } void IList.Remove(object item) { throw new NotSupportedException(); } void IList.Insert(int index, object item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } #endregion } }