diff --git a/Source/Collections/AsyncVirtualObservableReadOnlyList.cs b/Source/Collections/AsyncVirtualObservableReadOnlyList.cs
deleted file mode 100644
index 4b19388..0000000
--- a/Source/Collections/AsyncVirtualObservableReadOnlyList.cs
+++ /dev/null
@@ -1,656 +0,0 @@
-#region Apache License 2.0
-/*
-Nuclex .NET Framework
-Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-#endregion // Apache License 2.0
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-
-#if !NO_SPECIALIZED_COLLECTIONS
-using System.Collections.Specialized;
-using System.Diagnostics;
-#endif
-
-namespace Nuclex.Support.Collections {
-
- ///
- /// List which fires events when items are added or removed, whilst also
- /// lazily fetching items as needed (for example from a socket or database)
- ///
- /// Type of items the list manages
- public abstract class AsyncVirtualObservableReadOnlyList :
- IList,
- IList,
- ICollection,
-#if !NO_SPECIALIZED_COLLECTIONS
- INotifyCollectionChanged,
-#endif
- IObservableCollection {
-
- #region class Enumerator
-
- /// Enumerates over the items in a virtual list
- private class Enumerator : IEnumerator, IEnumerator {
-
- /// Initializes a new virtual list enumerator
- /// List whose items will be enumerated
- public Enumerator(AsyncVirtualObservableReadOnlyList virtualList) {
- this.virtualList = virtualList;
- this.currentItemIndex = -1;
- this.lastMoveNextResult = false;
-
- Reset();
- }
-
- /// Immediately releases all resources owned by the instance
- public void Dispose() {
- this.virtualList = null;
- }
-
- /// The item at the enumerator's current position
- public TItem Current {
- get {
-#if DEBUG
- checkVersion();
-#endif
-
- if(this.lastMoveNextResult == false) {
- throw new InvalidOperationException("Enumerator is not on a valid position");
- }
-
- this.virtualList.requireCount();
- this.virtualList.requirePage(this.currentItemIndex / this.virtualList.pageSize);
-
- return this.virtualList.typedList[this.currentItemIndex];
- }
- }
-
- /// Advances the enumerator to the next item
- /// True if there was a next item
- public bool MoveNext() {
-#if DEBUG
- checkVersion();
-#endif
-
- this.virtualList.requireCount();
-
- // Go forward if there potentially are items remaining. The count may still be
- // unreliable at this point (due to the uncertain count mechanism that truncates
- // the list when fetching items finds an earlier end of the list)
- if(this.currentItemIndex < this.virtualList.assumedCount.Value) {
-
- // If the enumerator's 'Current' property is never used an the virtual list
- // uses the dynamic truncation features (for unknown list sizes), then this
- // enumerator could be moved way past the last element via 'MoveNext()'.
- this.virtualList.requirePage(this.currentItemIndex / this.virtualList.pageSize);
- ++this.currentItemIndex; // Accept potentially advancing past the end here
-
- }
-
- // Are we on a valid item? If so, return true to indicate the list continued,
- // otherwise we must have hit the end (or are already past it).
- return this.lastMoveNextResult = (
- (this.currentItemIndex < this.virtualList.assumedCount.Value)
- );
- }
-
- /// Resets the enumerator to its initial position
- public void Reset() {
- this.currentItemIndex = -1;
-#if DEBUG
- this.expectedVersion = this.virtualList.version;
-#endif
- }
-
- /// The item at the enumerator's current position
- object IEnumerator.Current {
- get { return Current; }
- }
-
-#if DEBUG
- /// Ensures that the virtual list has not changed
- private void checkVersion() {
- if(this.expectedVersion != this.virtualList.version)
- throw new InvalidOperationException("Virtual list has been modified");
- }
-#endif
-
- /// Virtual list the enumerator belongs to
- private AsyncVirtualObservableReadOnlyList virtualList;
- /// Index of the item the enumerator currently is in
- private int currentItemIndex;
- /// The most recent result returned from MoveNext()
- private bool lastMoveNextResult;
-#if DEBUG
- /// Version the virtual list is expected to have
- private int expectedVersion;
-#endif
- }
-
- #endregion // class Enumerator
-
- /// Raised when an item has been added to the collection
- public event EventHandler> ItemAdded;
- /// Raised when an item is removed from the collection
- public event EventHandler> ItemRemoved;
- /// Raised when an item is replaced in the collection
- public event EventHandler> ItemReplaced;
- /// Raised when the collection is about to be cleared
- ///
- /// This could be covered by calling ItemRemoved for each item currently
- /// contained in the collection, but it is often simpler and more efficient
- /// to process the clearing of the entire collection as a special operation.
- ///
- public event EventHandler Clearing;
- /// Raised when the collection has been cleared
- public event EventHandler Cleared;
-
-#if !NO_SPECIALIZED_COLLECTIONS
- /// Called when the collection has changed
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-#endif
-
- ///
- /// Initializes a new instance of the ObservableList class that is empty.
- ///
- ///
- /// How many items to download in one batch
- ///
- ///
- /// The can be set to one to request items
- /// individually or to a larger value in order to improve efficiency when
- /// the source of the items is a database or similar source that gains
- /// performance from requesting multiple items at once.
- ///
- public AsyncVirtualObservableReadOnlyList(int pageSize = 32) {
- this.typedList = new List();
- this.objectList = this.typedList as IList;
- this.pageSize = pageSize;
- }
-
- ///
- /// Marks all items as non-fetched, causing them to be requested again
- ///
- ///
- /// Whether to also clear the items that may already be in memory but would
- /// get overwritten on the next fetch. If items consume a lot of memory, this
- /// will make them available for garbage collection.
- ///
- public void InvalidateAll(bool purgeItems = false) {
- if(this.assumedCount.HasValue) { // If not fetched before, no action needed
- int pageCount = this.fetchedPages.Length;
- for(int index = 0; index < pageCount; ++index) {
- this.fetchedPages[index] = false;
- }
-
- if(purgeItems) {
- int itemCount = this.assumedCount.Value;
- for(int index = 0; index < itemCount; ++index) {
- this.typedList[index] = default(TItem);
- }
- }
- }
- }
-
- ///
- /// Marks an items as non-fetched, causing it to be requested again on access
- ///
- ///
- /// Index of the item that will be marked as non-fetched
- ///
- ///
- /// Whether to also clear the items that may already be in memory but would
- /// get overwritten on the next fetch. If items consume a lot of memory, this
- /// will make them available for garbage collection.
- ///
- ///
- /// Since the list works in pages, this will actually mark the whole page as
- /// non-fetched, causing all items in the same page to be requested again
- /// when any of them are next accessed.
- ///
- public void Invalidate(int itemIndex, bool purgeItems = false) {
- if(this.assumedCount.HasValue) { // If not fetched before, no action needed
- int pageIndex = itemIndex / this.pageSize;
- this.fetchedPages[pageIndex] = false;
-
- if(purgeItems) {
- int count = Math.Min(
- this.assumedCount.Value - (this.pageSize * pageIndex),
- this.pageSize
- );
- for(int index = itemIndex / this.pageSize; index < count; ++index) {
- this.typedList[index] = default(TItem);
- }
- }
- }
- }
-
- /// Determines the index of the specified item in the list
- /// Item whose index will be determined
- /// The index of the item in the list or -1 if not found
- public int IndexOf(TItem item) {
- return this.typedList.IndexOf(item);
- }
-
- /// Inserts an item into the list at the specified index
- /// Index the item will be inserted at
- /// Item that will be inserted into the list
- public void Insert(int index, TItem item) {
- throw new NotSupportedException("Cannot insert items into a read-only list");
- }
-
- /// Removes the item at the specified index from the list
- /// Index at which the item will be removed
- public void RemoveAt(int index) {
- throw new NotSupportedException("Cannot remove items from a read-only list");
- }
-
- /// Accesses the item at the specified index in the list
- /// Index of the item that will be accessed
- /// The item at the specified index
- public TItem this[int index] {
- get {
- requireCount();
- requirePage(index / this.pageSize);
-
- return this.typedList[index];
- }
- set {
- // Make sure the page is fetched, otherwise, the item would only suddenly
- // revert to its state in the source when the pages around it is fetchd later.
- requireCount();
- requirePage(index / this.pageSize);
-#if DEBUG
- ++this.version;
-#endif
- TItem oldItem = this.typedList[index];
- this.typedList[index] = value;
- OnReplaced(oldItem, value, index);
- }
- }
-
- /// Adds an item to the end of the list
- /// Item that will be added to the list
- public void Add(TItem item) {
- throw new NotSupportedException("Cannot add items to a read-only list");
- }
-
- /// Removes all items from the list
- public void Clear() {
- throw new NotSupportedException("Cannot clear a read-only list");
- }
-
- /// Checks whether the list contains the specified item
- /// Item the list will be checked for
- /// True if the list contains the specified items
- public bool Contains(TItem item) {
- requireCount();
- requireAllPages();
-
- return this.typedList.Contains(item);
- }
-
- /// Copies the contents of the list into an array
- /// Array the list will be copied into
- ///
- /// Index in the target array where the first item will be copied to
- ///
- public void CopyTo(TItem[] array, int arrayIndex) {
- requireCount();
- requireAllPages();
-
- this.typedList.CopyTo(array, arrayIndex);
- }
-
- /// Total number of items in the list
- public int Count {
- get {
- requireCount();
- return this.assumedCount.Value;
- }
- }
-
- /// Whether the list is a read-only list
- public bool IsReadOnly {
- get { return this.typedList.IsReadOnly; }
- }
-
- /// Removes the specified item from the list
- /// Item that will be removed from the list
- ///
- /// True if the item was found and removed from the list, false otherwise
- ///
- public bool Remove(TItem item) {
- throw new NotSupportedException("Cannot remove items from a read-only list");
- }
-
- /// Returns an enumerator for the items in the list
- /// An enumerator for the list's items
- public IEnumerator GetEnumerator() {
- return new Enumerator(this);
- }
-
- #region IEnumerable implementation
-
- /// Returns an enumerator for the items in the list
- /// An enumerator for the list's items
- IEnumerator IEnumerable.GetEnumerator() {
- return new Enumerator(this);
- }
-
- #endregion // IEnumerable implementation
-
- #region ICollection implementation
-
- /// Copies the contents of the list into an array
- /// Array the list will be copied into
- ///
- /// Index in the target array where the first item will be copied to
- ///
- void ICollection.CopyTo(Array array, int arrayIndex) {
- requireCount();
- requireAllPages();
-
- this.objectList.CopyTo(array, arrayIndex);
- }
-
- /// Whether this list performs thread synchronization
- bool ICollection.IsSynchronized {
- get { return this.objectList.IsSynchronized; }
- }
-
- /// Synchronization root used by the list to synchronize threads
- object ICollection.SyncRoot {
- get { return this.objectList.SyncRoot; }
- }
-
- #endregion // ICollection implementation
-
- #region IList implementation
-
- /// Adds an item to the list
- /// Item that will be added to the list
- ///
- /// The position at which the item has been inserted or -1 if the item was not inserted
- ///
- int IList.Add(object value) {
- throw new NotSupportedException("Cannot add items into a read-only list");
- }
-
- /// Checks whether the list contains the specified item
- /// Item the list will be checked for
- /// True if the list contains the specified items
- bool IList.Contains(object item) {
- requireCount();
- requireAllPages();
- return this.objectList.Contains(item);
- }
-
- /// Determines the index of the specified item in the list
- /// Item whose index will be determined
- /// The index of the item in the list or -1 if not found
- int IList.IndexOf(object item) {
- requireCount();
- requireAllPages();
- return this.objectList.IndexOf(item);
- }
-
- /// Inserts an item into the list at the specified index
- /// Index the item will be inserted at
- /// Item that will be inserted into the list
- void IList.Insert(int index, object item) {
- throw new NotSupportedException("Cannot insert items into a read-only list");
- }
-
- /// Whether the list is of a fixed size
- bool IList.IsFixedSize {
- get { return this.objectList.IsFixedSize; }
- }
-
- /// Removes the specified item from the list
- /// Item that will be removed from the list
- void IList.Remove(object item) {
- throw new NotSupportedException("Cannot remove items from a read-only list");
- }
-
- /// Accesses the item at the specified index in the list
- /// Index of the item that will be accessed
- /// The item at the specified index
- object IList.this[int index] {
- get {
- requireCount();
- requirePage(index / this.pageSize);
-
- return this.objectList[index];
- }
- set {
- // Make sure the page is fetched, otherwise, the item would only suddenly
- // revert to its state in the source when the pages around it is fetchd later.
- requireCount();
- requirePage(index / this.pageSize);
-#if DEBUG
- ++this.version;
-#endif
- TItem oldItem = this.typedList[index];
- this.objectList[index] = value;
- TItem newItem = this.typedList[index];
- OnReplaced(oldItem, newItem, index);
- }
- }
-
- #endregion // IList implementation
-
- /// Fires the 'ItemAdded' event
- /// Item that has been added to the collection
- /// Index of the added item
- protected virtual void OnAdded(TItem item, int index) {
- if(ItemAdded != null) {
- ItemAdded(this, new ItemEventArgs(item));
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(
- this,
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
- );
- }
-#endif
- }
-
- /// Fires the 'ItemRemoved' event
- /// Item that has been removed from the collection
- /// Index the item has been removed from
- protected virtual void OnRemoved(TItem item, int index) {
- if(ItemRemoved != null) {
- ItemRemoved(this, new ItemEventArgs(item));
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(
- this,
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
- );
- }
-#endif
- }
-
- /// Fires the 'ItemReplaced' event
- /// Item that has been replaced
- /// New item the original item was replaced with
- /// Index of the replaced item
- protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
- if(ItemReplaced != null) {
- ItemReplaced(this, new ItemReplaceEventArgs(oldItem, newItem));
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(
- this,
- new NotifyCollectionChangedEventArgs(
- NotifyCollectionChangedAction.Replace, newItem, oldItem, index
- )
- );
- }
-#endif
- }
-
- /// Fires the 'Clearing' event
- protected virtual void OnClearing() {
- if(Clearing != null) {
- Clearing(this, EventArgs.Empty);
- }
- }
-
- /// Fires the 'Cleared' event
- protected virtual void OnCleared() {
- if(Cleared != null) {
- Cleared(this, EventArgs.Empty);
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
- }
-#endif
- }
-
- /// Counts the total number of items in the virtual collection
- /// The total number of items
- protected abstract int CountItems();
-
- /// Fetches a page required by the collection
- /// List into which the items should be fetched
- ///
- /// Index of the first item to fetch. This is both the start index in
- /// the actual data and the element index at which to write into the list.
- ///
- /// Number of items that should be fetched
- /// The number of items that were actually fetched
- ///
- /// If you fetch fewer than the requested number of items here, you will immediately
- /// truncate the entire list (it will assume that the end was reached, a means
- /// to support cases where the total number is not known). Fetching more than
- /// the requested number of items will just put items in memory that the list will
- /// continue to think are empty and fetch again if they are actually accessed.
- ///
- protected abstract Task FetchItemsAsync(
- IList target, int startIndex, int count
- );
-
- /// Requests placeholder items to be created
- /// List into which the items should be created
- ///
- /// Index of the first item to create a placeholder for. This is both the start index
- /// in the actual data and the element index at which to write into the list.
- ///
- /// Number of items that should be crated
- ///
- /// When you requests items that have not been fetched yet, this method will be
- /// called synchronously to create placeholder items (these could be empty items
- /// or items with a 'StillLoading' flag set. This allows any UI controls showing
- /// the collection's contents to immediately display the items while fetching
- /// happens in the background and will replace the placeholder once complete.
- ///
- protected abstract void CreatePlaceholderItems(
- IList target, int startIndex, int count
- );
-
- /// Ensures that the total number of items is known
- private void requireCount() {
- if(!this.assumedCount.HasValue) {
- int itemCount = CountItems();
- this.assumedCount = itemCount;
-
- int pageCount = (itemCount + this.pageSize - 1) / this.pageSize;
- this.fetchedPages = new bool[pageCount];
- }
- }
-
- /// Ensures that all items have fetched
- ///
- /// Avoid if possible.
- ///
- private void requireAllPages() {
- Debug.Assert(
- this.assumedCount.HasValue,
- "This method should only be called when item count is already known"
- );
- int pageCount = this.fetchedPages.Length;
- for(int index = 0; index < pageCount; ++index) {
- requirePage(index);
- // CHECK: Should we throttle this by constructing a clever chain of
- // ContinueWith() tasks so we don't cause a flood of async requests?
- }
- }
-
- /// Ensures that the specified page has been fetched
- /// Index of the page that needs to be fetched
- private async void requirePage(int pageIndex) {
- Debug.Assert(
- this.assumedCount.HasValue,
- "This method should only be called when item count is already known"
- );
- if(this.fetchedPages[pageIndex]) {
- return;
- }
-
- int startIndex = pageIndex * this.pageSize;
- int count = Math.Min(this.assumedCount.Value - startIndex, this.pageSize);
- CreatePlaceholderItems(this.typedList, pageIndex * this.pageSize, count);
-
- this.fetchedPages[pageIndex] = true; // Prevent double-fetch
-
- // Send out change notifications that the items have been replaced
- // (at this point, they're only placeholder items, of course)
- var placeholderItems = new List(count);
- for(int index = startIndex; index < count; ++index) {
- placeholderItems[index - startIndex] = this.typedList[index];
- OnReplaced(default(TItem), this.typedList[index], index);
- }
-
- int fetchedItemCount = await FetchItemsAsync(this.typedList, startIndex, count);
- if(fetchedItemCount < this.pageSize) {
- this.assumedCount = startIndex + fetchedItemCount;
- }
-
- // The count may have been adjusted if this truncated the list,
- // so recalculate the actual number of items. Then send out change
- // notifications for the items that have now been fetched.
- count = Math.Min(this.assumedCount.Value - startIndex, this.pageSize);
- for(int index = startIndex; index < count; ++index) {
- OnReplaced(placeholderItems[index - startIndex], this.typedList[index], index);
- }
- }
-
- /// Number of items the collection believes it has
- private int? assumedCount;
- /// Number of items to fetch in a single request
- private readonly int pageSize;
- /// Tracks which pages have been fetched so far
- private bool[] fetchedPages;
- /// The wrapped list under its type-safe interface
- private IList typedList;
- /// The wrapped list under its object interface
- private IList objectList;
-#if DEBUG
- /// Used to detect when enumerators go out of sync
- private int version;
-#endif
-
- }
-
-} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/VirtualObservableReadOnlyList.cs b/Source/Collections/VirtualObservableReadOnlyList.cs
deleted file mode 100644
index 4469b6f..0000000
--- a/Source/Collections/VirtualObservableReadOnlyList.cs
+++ /dev/null
@@ -1,617 +0,0 @@
-#region Apache License 2.0
-/*
-Nuclex .NET Framework
-Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-#endregion // Apache License 2.0
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-
-#if !NO_SPECIALIZED_COLLECTIONS
-using System.Collections.Specialized;
-using System.Diagnostics;
-#endif
-
-namespace Nuclex.Support.Collections {
-
- ///
- /// List which fires events when items are added or removed, whilst also
- /// lazily fetching items as needed (for example from a socket or database)
- ///
- /// Type of items the list manages
- public abstract class VirtualObservableReadOnlyList :
- IList,
- IList,
- ICollection,
-#if !NO_SPECIALIZED_COLLECTIONS
- INotifyCollectionChanged,
-#endif
- IObservableCollection {
-
- #region class Enumerator
-
- /// Enumerates over the items in a virtual list
- private class Enumerator : IEnumerator, IEnumerator {
-
- /// Initializes a new virtual list enumerator
- /// List whose items will be enumerated
- public Enumerator(VirtualObservableReadOnlyList virtualList) {
- this.virtualList = virtualList;
- this.currentItemIndex = -1;
- this.lastMoveNextResult = false;
-
- Reset();
- }
-
- /// Immediately releases all resources owned by the instance
- public void Dispose() {
- this.virtualList = null;
- }
-
- /// The item at the enumerator's current position
- public TItem Current {
- get {
-#if DEBUG
- checkVersion();
-#endif
-
- if(this.lastMoveNextResult == false) {
- throw new InvalidOperationException("Enumerator is not on a valid position");
- }
-
- this.virtualList.requireCount();
- this.virtualList.requirePage(this.currentItemIndex / this.virtualList.pageSize);
-
- return this.virtualList.typedList[this.currentItemIndex];
- }
- }
-
- /// Advances the enumerator to the next item
- /// True if there was a next item
- public bool MoveNext() {
-#if DEBUG
- checkVersion();
-#endif
-
- this.virtualList.requireCount();
-
- // Go forward if there potentially are items remaining. The count may still be
- // unreliable at this point (due to the uncertain count mechanism that truncates
- // the list when fetching items finds an earlier end of the list)
- if(this.currentItemIndex < this.virtualList.assumedCount.Value) {
-
- // If the enumerator's 'Current' property is never used an the virtual list
- // uses the dynamic truncation features (for unknown list sizes), then this
- // enumerator could be moved way past the last element via 'MoveNext()'.
- this.virtualList.requirePage(this.currentItemIndex / this.virtualList.pageSize);
- ++this.currentItemIndex; // Accept potentially advancing past the end here
-
- }
-
- // Are we on a valid item? If so, return true to indicate the list continued,
- // otherwise we must have hit the end (or are already past it).
- return this.lastMoveNextResult = (
- (this.currentItemIndex < this.virtualList.assumedCount.Value)
- );
- }
-
- /// Resets the enumerator to its initial position
- public void Reset() {
- this.currentItemIndex = -1;
-#if DEBUG
- this.expectedVersion = this.virtualList.version;
-#endif
- }
-
- /// The item at the enumerator's current position
- object IEnumerator.Current {
- get { return Current; }
- }
-
-#if DEBUG
- /// Ensures that the virtual list has not changed
- private void checkVersion() {
- if(this.expectedVersion != this.virtualList.version)
- throw new InvalidOperationException("Virtual list has been modified");
- }
-#endif
-
- /// Virtual list the enumerator belongs to
- private VirtualObservableReadOnlyList virtualList;
- /// Index of the item the enumerator currently is in
- private int currentItemIndex;
- /// The most recent result returned from MoveNext()
- private bool lastMoveNextResult;
-#if DEBUG
- /// Version the virtual list is expected to have
- private int expectedVersion;
-#endif
- }
-
- #endregion // class Enumerator
-
- /// Raised when an item has been added to the collection
- public event EventHandler> ItemAdded;
- /// Raised when an item is removed from the collection
- public event EventHandler> ItemRemoved;
- /// Raised when an item is replaced in the collection
- public event EventHandler> ItemReplaced;
- /// Raised when the collection is about to be cleared
- ///
- /// This could be covered by calling ItemRemoved for each item currently
- /// contained in the collection, but it is often simpler and more efficient
- /// to process the clearing of the entire collection as a special operation.
- ///
- public event EventHandler Clearing;
- /// Raised when the collection has been cleared
- public event EventHandler Cleared;
-
-#if !NO_SPECIALIZED_COLLECTIONS
- /// Called when the collection has changed
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-#endif
-
- ///
- /// Initializes a new instance of the ObservableList class that is empty.
- ///
- ///
- /// How many items to download in one batch
- ///
- ///
- /// The can be set to one to request items
- /// individually or to a larger value in order to improve efficiency when
- /// the source of the items is a database or similar source that gains
- /// performance from requesting multiple items at once.
- ///
- public VirtualObservableReadOnlyList(int pageSize = 32) {
- this.typedList = new List();
- this.objectList = this.typedList as IList;
- this.pageSize = pageSize;
- }
-
- ///
- /// Marks all items as non-fetched, causing them to be requested again
- ///
- ///
- /// Whether to also clear the items that may already be in memory but would
- /// get overwritten on the next fetch. If items consume a lot of memory, this
- /// will make them available for garbage collection.
- ///
- public void InvalidateAll(bool purgeItems = false) {
- if(this.assumedCount.HasValue) { // If not fetched before, no action needed
- int pageCount = this.fetchedPages.Length;
- for(int index = 0; index < pageCount; ++index) {
- this.fetchedPages[index] = false;
- }
-
- if(purgeItems) {
- int itemCount = this.assumedCount.Value;
- for(int index = 0; index < itemCount; ++index) {
- this.typedList[index] = default(TItem);
- }
- }
- }
- }
-
- ///
- /// Marks an items as non-fetched, causing it to be requested again on access
- ///
- ///
- /// Index of the item that will be marked as non-fetched
- ///
- ///
- /// Whether to also clear the items that may already be in memory but would
- /// get overwritten on the next fetch. If items consume a lot of memory, this
- /// will make them available for garbage collection.
- ///
- ///
- /// Since the list works in pages, this will actually mark the whole page as
- /// non-fetched, causing all items in the same page to be requested again
- /// when any of them are next accessed.
- ///
- public void Invalidate(int itemIndex, bool purgeItems = false) {
- if(this.assumedCount.HasValue) { // If not fetched before, no action needed
- int pageIndex = itemIndex / this.pageSize;
- this.fetchedPages[pageIndex] = false;
-
- if(purgeItems) {
- int count = Math.Min(
- this.assumedCount.Value - (this.pageSize * pageIndex),
- this.pageSize
- );
- for(int index = itemIndex / this.pageSize; index < count; ++index) {
- this.typedList[index] = default(TItem);
- }
- }
- }
- }
-
- /// Determines the index of the specified item in the list
- /// Item whose index will be determined
- /// The index of the item in the list or -1 if not found
- public int IndexOf(TItem item) {
- return this.typedList.IndexOf(item);
- }
-
- /// Inserts an item into the list at the specified index
- /// Index the item will be inserted at
- /// Item that will be inserted into the list
- public void Insert(int index, TItem item) {
- throw new NotSupportedException("Cannot insert items into a read-only list");
- }
-
- /// Removes the item at the specified index from the list
- /// Index at which the item will be removed
- public void RemoveAt(int index) {
- throw new NotSupportedException("Cannot remove items from a read-only list");
- }
-
- /// Accesses the item at the specified index in the list
- /// Index of the item that will be accessed
- /// The item at the specified index
- public TItem this[int index] {
- get {
- requireCount();
- requirePage(index / this.pageSize);
-
- return this.typedList[index];
- }
- set {
- // Make sure the page is fetched, otherwise, the item would only suddenly
- // revert to its state in the source when the pages around it is fetchd later.
- requireCount();
- requirePage(index / this.pageSize);
-#if DEBUG
- ++this.version;
-#endif
- TItem oldItem = this.typedList[index];
- this.typedList[index] = value;
- OnReplaced(oldItem, value, index);
- }
- }
-
- /// Adds an item to the end of the list
- /// Item that will be added to the list
- public void Add(TItem item) {
- throw new NotSupportedException("Cannot add items to a read-only list");
- }
-
- /// Removes all items from the list
- public void Clear() {
- throw new NotSupportedException("Cannot clear a read-only list");
- }
-
- /// Checks whether the list contains the specified item
- /// Item the list will be checked for
- /// True if the list contains the specified items
- public bool Contains(TItem item) {
- requireCount();
- requireAllPages();
-
- return this.typedList.Contains(item);
- }
-
- /// Copies the contents of the list into an array
- /// Array the list will be copied into
- ///
- /// Index in the target array where the first item will be copied to
- ///
- public void CopyTo(TItem[] array, int arrayIndex) {
- requireCount();
- requireAllPages();
-
- this.typedList.CopyTo(array, arrayIndex);
- }
-
- /// Total number of items in the list
- public int Count {
- get {
- requireCount();
- return this.assumedCount.Value;
- }
- }
-
- /// Whether the list is a read-only list
- public bool IsReadOnly {
- get { return this.typedList.IsReadOnly; }
- }
-
- /// Removes the specified item from the list
- /// Item that will be removed from the list
- ///
- /// True if the item was found and removed from the list, false otherwise
- ///
- public bool Remove(TItem item) {
- throw new NotSupportedException("Cannot remove items from a read-only list");
- }
-
- /// Returns an enumerator for the items in the list
- /// An enumerator for the list's items
- public IEnumerator GetEnumerator() {
- return this.typedList.GetEnumerator(); // TODO
- }
-
- #region IEnumerable implementation
-
- /// Returns an enumerator for the items in the list
- /// An enumerator for the list's items
- IEnumerator IEnumerable.GetEnumerator() {
- return this.objectList.GetEnumerator(); // TODO
- }
-
- #endregion // IEnumerable implementation
-
- #region ICollection implementation
-
- /// Copies the contents of the list into an array
- /// Array the list will be copied into
- ///
- /// Index in the target array where the first item will be copied to
- ///
- void ICollection.CopyTo(Array array, int arrayIndex) {
- requireCount();
- requireAllPages();
-
- this.objectList.CopyTo(array, arrayIndex);
- }
-
- /// Whether this list performs thread synchronization
- bool ICollection.IsSynchronized {
- get { return this.objectList.IsSynchronized; }
- }
-
- /// Synchronization root used by the list to synchronize threads
- object ICollection.SyncRoot {
- get { return this.objectList.SyncRoot; }
- }
-
- #endregion // ICollection implementation
-
- #region IList implementation
-
- /// Adds an item to the list
- /// Item that will be added to the list
- ///
- /// The position at which the item has been inserted or -1 if the item was not inserted
- ///
- int IList.Add(object value) {
- throw new NotSupportedException("Cannot add items into a read-only list");
- }
-
- /// Checks whether the list contains the specified item
- /// Item the list will be checked for
- /// True if the list contains the specified items
- bool IList.Contains(object item) {
- requireCount();
- requireAllPages();
- return this.objectList.Contains(item);
- }
-
- /// Determines the index of the specified item in the list
- /// Item whose index will be determined
- /// The index of the item in the list or -1 if not found
- int IList.IndexOf(object item) {
- requireCount();
- requireAllPages();
- return this.objectList.IndexOf(item);
- }
-
- /// Inserts an item into the list at the specified index
- /// Index the item will be inserted at
- /// Item that will be inserted into the list
- void IList.Insert(int index, object item) {
- throw new NotSupportedException("Cannot insert items into a read-only list");
- }
-
- /// Whether the list is of a fixed size
- bool IList.IsFixedSize {
- get { return this.objectList.IsFixedSize; }
- }
-
- /// Removes the specified item from the list
- /// Item that will be removed from the list
- void IList.Remove(object item) {
- throw new NotSupportedException("Cannot remove items from a read-only list");
- }
-
- /// Accesses the item at the specified index in the list
- /// Index of the item that will be accessed
- /// The item at the specified index
- object IList.this[int index] {
- get {
- requireCount();
- requirePage(index / this.pageSize);
-
- return this.objectList[index];
- }
- set {
- // Make sure the page is fetched, otherwise, the item would only suddenly
- // revert to its state in the source when the pages around it is fetchd later.
- requireCount();
- requirePage(index / this.pageSize);
-#if DEBUG
- ++this.version;
-#endif
- TItem oldItem = this.typedList[index];
- this.objectList[index] = value;
- TItem newItem = this.typedList[index];
- OnReplaced(oldItem, newItem, index);
- }
- }
-
- #endregion // IList implementation
-
- /// Fires the 'ItemAdded' event
- /// Item that has been added to the collection
- /// Index of the added item
- protected virtual void OnAdded(TItem item, int index) {
- if(ItemAdded != null) {
- ItemAdded(this, new ItemEventArgs(item));
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(
- this,
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
- );
- }
-#endif
- }
-
- /// Fires the 'ItemRemoved' event
- /// Item that has been removed from the collection
- /// Index the item has been removed from
- protected virtual void OnRemoved(TItem item, int index) {
- if(ItemRemoved != null) {
- ItemRemoved(this, new ItemEventArgs(item));
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(
- this,
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
- );
- }
-#endif
- }
-
- /// Fires the 'ItemReplaced' event
- /// Item that has been replaced
- /// New item the original item was replaced with
- /// Index of the replaced item
- protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
- if(ItemReplaced != null) {
- ItemReplaced(this, new ItemReplaceEventArgs(oldItem, newItem));
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(
- this,
- new NotifyCollectionChangedEventArgs(
- NotifyCollectionChangedAction.Replace, newItem, oldItem, index
- )
- );
- }
-#endif
- }
-
- /// Fires the 'Clearing' event
- protected virtual void OnClearing() {
- if(Clearing != null) {
- Clearing(this, EventArgs.Empty);
- }
- }
-
- /// Fires the 'Cleared' event
- protected virtual void OnCleared() {
- if(Cleared != null) {
- Cleared(this, EventArgs.Empty);
- }
-#if !NO_SPECIALIZED_COLLECTIONS
- if(CollectionChanged != null) {
- CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
- }
-#endif
- }
-
- /// Counts the total number of items in the virtual collection
- /// The total number of items
- protected abstract int CountItems();
-
- /// Fetches a page required by the collection
- /// List into which the items should be fetched
- ///
- /// Index of the first item to fetch. This is both the start index in
- /// the actual data and the element index at which to write into the list.
- ///
- /// Number of items that should be fetched
- /// The number of items that were actually fetched
- ///
- /// If you fetch fewer than the requested number of items here, you will immediately
- /// truncate the entire list (it will assume that the end was reached, a means
- /// to support cases where the total number is not known). Fetching more than
- /// the requested number of items will just put items in memory that the list will
- /// continue to think are empty and fetch again if they are actually accessed.
- ///
- protected abstract int FetchItems(IList target, int startIndex, int count);
-
- /// Ensures that the total number of items is known
- private void requireCount() {
- if(!this.assumedCount.HasValue) {
- int itemCount = CountItems();
- this.assumedCount = itemCount;
-
- int pageCount = (itemCount + this.pageSize - 1) / this.pageSize;
- this.fetchedPages = new bool[pageCount];
- }
- }
-
- /// Ensures that all items have fetched
- ///
- /// Avoid if possible.
- ///
- private void requireAllPages() {
- Debug.Assert(
- this.assumedCount.HasValue,
- "This method should only be called when item count is already known"
- );
- int pageCount = this.fetchedPages.Length;
- for(int index = 0; index < pageCount; ++index) {
- requirePage(index);
- }
- }
-
- /// Ensures that the specified page has been fetched
- /// Index of the page that needs to be fetched
- private void requirePage(int pageIndex) {
- Debug.Assert(
- this.assumedCount.HasValue,
- "This method should only be called when item count is already known"
- );
- if(!this.fetchedPages[pageIndex]) {
- int count = Math.Min(
- this.assumedCount.Value - (this.pageSize * pageIndex),
- this.pageSize
- );
-
- int fetchedItemCount = FetchItems(this.typedList, pageIndex * this.pageSize, count);
- if(fetchedItemCount < this.pageSize) {
- this.assumedCount = pageIndex * this.pageSize + fetchedItemCount;
- }
-
- this.fetchedPages[pageIndex] = true;
- }
- }
-
- /// Number of items the collection believes it has
- private int? assumedCount;
- /// Number of items to fetch in a single request
- private readonly int pageSize;
- /// Tracks which pages have been fetched so far
- private bool[] fetchedPages;
- /// The wrapped list under its type-safe interface
- private IList typedList;
- /// The wrapped list under its object interface
- private IList objectList;
-#if DEBUG
- /// Used to detect when enumerators go out of sync
- private int version;
-#endif
-
- }
-
-} // namespace Nuclex.Support.Collections