diff --git a/Nuclex.Avalonia (netstandard-2.0).csproj b/Nuclex.Avalonia (netstandard-2.0).csproj
index 54ab9d9..fdae37b 100644
--- a/Nuclex.Avalonia (netstandard-2.0).csproj
+++ b/Nuclex.Avalonia (netstandard-2.0).csproj
@@ -9,7 +9,7 @@
Nuclex.Avalonia
obj\source
enable
- 8.0
+ 9.0
@@ -25,6 +25,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Source/Collections/AsyncVirtualObservableReadOnlyList.cs b/Source/Collections/AsyncVirtualObservableReadOnlyList.cs
index 1171f1b..7cb43bd 100644
--- a/Source/Collections/AsyncVirtualObservableReadOnlyList.cs
+++ b/Source/Collections/AsyncVirtualObservableReadOnlyList.cs
@@ -20,12 +20,16 @@ limitations under the License.
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
#if !NO_SPECIALIZED_COLLECTIONS
using System.Collections.Specialized;
-using System.Diagnostics;
#endif
+using Nuclex.Support.Collections;
+
namespace Nuclex.Avalonia.Collections {
///
@@ -59,7 +63,7 @@ namespace Nuclex.Avalonia.Collections {
/// Immediately releases all resources owned by the instance
public void Dispose() {
- this.virtualList = null;
+ this.virtualList = null!; // Only to make life easier got the GC
}
/// The item at the enumerator's current position
@@ -119,7 +123,7 @@ namespace Nuclex.Avalonia.Collections {
/// The item at the enumerator's current position
object IEnumerator.Current {
- get { return Current; }
+ get { return Current!; } // No idea what the compiler's issue is here
}
#if DEBUG
@@ -145,24 +149,24 @@ namespace Nuclex.Avalonia.Collections {
#endregion // class Enumerator
/// Raised when an item has been added to the collection
- public event EventHandler> ItemAdded;
+ public event EventHandler>? ItemAdded;
/// Raised when an item is removed from the collection
- public event EventHandler> ItemRemoved;
+ public event EventHandler>? ItemRemoved;
/// Raised when an item is replaced in the collection
- public event EventHandler> ItemReplaced;
+ 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;
+ public event EventHandler? Clearing;
/// Raised when the collection has been cleared
- public event EventHandler Cleared;
+ public event EventHandler? Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
/// Called when the collection has changed
- public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event NotifyCollectionChangedEventHandler? CollectionChanged;
#endif
///
@@ -178,9 +182,10 @@ namespace Nuclex.Avalonia.Collections {
/// performance from requesting multiple items at once.
///
public AsyncVirtualObservableReadOnlyList(int pageSize = 32) {
- this.typedList = new List();
- this.objectList = this.typedList as IList;
+ this.typedList = new TItem[0];
+ this.objectList = (IList)this.typedList;
this.pageSize = pageSize;
+ this.fetchedPages = new bool[0];
}
///
@@ -201,7 +206,7 @@ namespace Nuclex.Avalonia.Collections {
if(purgeItems) {
int itemCount = this.assumedCount.Value;
for(int index = 0; index < itemCount; ++index) {
- this.typedList[index] = default(TItem);
+ this.typedList[index] = default(TItem)!; // not going to be exposed to users
}
}
}
@@ -234,7 +239,7 @@ namespace Nuclex.Avalonia.Collections {
this.pageSize
);
for(int index = itemIndex / this.pageSize; index < count; ++index) {
- this.typedList[index] = default(TItem);
+ this.typedList[index] = default(TItem)!; // not going to be exposed to users
}
}
}
@@ -244,7 +249,19 @@ namespace Nuclex.Avalonia.Collections {
/// 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);
+ requireCount();
+ requireAllPages();
+
+ // TODO: this won't work, it will compare the placeholder items :-/
+
+ IComparer itemComparer = Comparer.Default;
+ for(int index = 0; index < this.assumedCount.Value; ++index) {
+ if(itemComparer.Compare(this.typedList[index], item) == 0) {
+ return index;
+ }
+ }
+
+ return -1;
}
/// Inserts an item into the list at the specified index
@@ -299,10 +316,7 @@ namespace Nuclex.Avalonia.Collections {
/// 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);
+ return (IndexOf(item) != -1);
}
/// Copies the contents of the list into an array
@@ -570,6 +584,7 @@ namespace Nuclex.Avalonia.Collections {
);
/// Ensures that the total number of items is known
+ [MemberNotNull(nameof(assumedCount))]
private void requireCount() {
if(!this.assumedCount.HasValue) {
int itemCount = CountItems();
@@ -589,11 +604,12 @@ namespace Nuclex.Avalonia.Collections {
this.assumedCount.HasValue,
"This method should only be called when item count is already known"
);
- int pageCount = this.fetchedPages.Length;
+
+ // We may find that the list is shorter than expected while requesting pages.
+ // But the results will come in asynchronously, so we can't wait for it.
+ int pageCount = (this.assumedCount!.Value + this.pageSize - 1) / this.pageSize;
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?
}
}
@@ -609,7 +625,7 @@ namespace Nuclex.Avalonia.Collections {
}
int startIndex = pageIndex * this.pageSize;
- int count = Math.Min(this.assumedCount.Value - startIndex, 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
@@ -619,7 +635,7 @@ namespace Nuclex.Avalonia.Collections {
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);
+ //OnReplaced(default(TItem), this.typedList[index], index);
}
int fetchedItemCount = await FetchItemsAsync(this.typedList, startIndex, count);
@@ -630,7 +646,7 @@ namespace Nuclex.Avalonia.Collections {
// 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);
+ count = Math.Min(this.assumedCount!.Value - startIndex, this.pageSize);
for(int index = startIndex; index < count; ++index) {
OnReplaced(placeholderItems[index - startIndex], this.typedList[index], index);
}
@@ -643,7 +659,7 @@ namespace Nuclex.Avalonia.Collections {
/// Tracks which pages have been fetched so far
private bool[] fetchedPages;
/// The wrapped list under its type-safe interface
- private IList typedList;
+ private TItem[] typedList;
/// The wrapped list under its object interface
private IList objectList;
#if DEBUG
diff --git a/Source/Collections/VirtualObservableReadOnlyList.cs b/Source/Collections/VirtualObservableReadOnlyList.cs
index dfc925f..3f7e668 100644
--- a/Source/Collections/VirtualObservableReadOnlyList.cs
+++ b/Source/Collections/VirtualObservableReadOnlyList.cs
@@ -20,12 +20,16 @@ limitations under the License.
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
#if !NO_SPECIALIZED_COLLECTIONS
using System.Collections.Specialized;
-using System.Diagnostics;
#endif
+using Nuclex.Support.Collections;
+
namespace Nuclex.Avalonia.Collections {
///
@@ -59,7 +63,7 @@ namespace Nuclex.Avalonia.Collections {
/// Immediately releases all resources owned by the instance
public void Dispose() {
- this.virtualList = null;
+ this.virtualList = null!; // Only to make life easier got the GC
}
/// The item at the enumerator's current position
@@ -119,7 +123,7 @@ namespace Nuclex.Avalonia.Collections {
/// The item at the enumerator's current position
object IEnumerator.Current {
- get { return Current; }
+ get { return Current!; } // No idea what the compiler's issue is here
}
#if DEBUG
@@ -145,24 +149,24 @@ namespace Nuclex.Avalonia.Collections {
#endregion // class Enumerator
/// Raised when an item has been added to the collection
- public event EventHandler> ItemAdded;
+ public event EventHandler>? ItemAdded;
/// Raised when an item is removed from the collection
- public event EventHandler> ItemRemoved;
+ public event EventHandler>? ItemRemoved;
/// Raised when an item is replaced in the collection
- public event EventHandler> ItemReplaced;
+ 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;
+ public event EventHandler? Clearing;
/// Raised when the collection has been cleared
- public event EventHandler Cleared;
+ public event EventHandler? Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
/// Called when the collection has changed
- public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event NotifyCollectionChangedEventHandler? CollectionChanged;
#endif
///
@@ -178,9 +182,10 @@ namespace Nuclex.Avalonia.Collections {
/// performance from requesting multiple items at once.
///
public VirtualObservableReadOnlyList(int pageSize = 32) {
- this.typedList = new List();
- this.objectList = this.typedList as IList;
+ this.typedList = new TItem[0];
+ this.objectList = (IList)this.typedList;
this.pageSize = pageSize;
+ this.fetchedPages = new bool[0];
}
///
@@ -201,7 +206,7 @@ namespace Nuclex.Avalonia.Collections {
if(purgeItems) {
int itemCount = this.assumedCount.Value;
for(int index = 0; index < itemCount; ++index) {
- this.typedList[index] = default(TItem);
+ this.typedList[index] = default(TItem)!; // not going to be exposed to users
}
}
}
@@ -234,7 +239,7 @@ namespace Nuclex.Avalonia.Collections {
this.pageSize
);
for(int index = itemIndex / this.pageSize; index < count; ++index) {
- this.typedList[index] = default(TItem);
+ this.typedList[index] = default(TItem)!; // not going to be exposed to users
}
}
}
@@ -244,7 +249,19 @@ namespace Nuclex.Avalonia.Collections {
/// 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);
+ requireCount();
+ requireAllPages();
+
+ // TODO: this won't work, it will compare the placeholder items :-/
+
+ IComparer itemComparer = Comparer.Default;
+ for(int index = 0; index < this.assumedCount.Value; ++index) {
+ if(itemComparer.Compare(this.typedList[index], item) == 0) {
+ return index;
+ }
+ }
+
+ return -1;
}
/// Inserts an item into the list at the specified index
@@ -299,10 +316,7 @@ namespace Nuclex.Avalonia.Collections {
/// 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);
+ return (IndexOf(item) != -1);
}
/// Copies the contents of the list into an array
@@ -342,7 +356,7 @@ namespace Nuclex.Avalonia.Collections {
/// Returns an enumerator for the items in the list
/// An enumerator for the list's items
public IEnumerator GetEnumerator() {
- return this.typedList.GetEnumerator(); // TODO
+ return new Enumerator(this);
}
#region IEnumerable implementation
@@ -550,6 +564,7 @@ namespace Nuclex.Avalonia.Collections {
protected abstract int FetchItems(IList target, int startIndex, int count);
/// Ensures that the total number of items is known
+ [MemberNotNull(nameof(assumedCount))]
private void requireCount() {
if(!this.assumedCount.HasValue) {
int itemCount = CountItems();
@@ -604,7 +619,7 @@ namespace Nuclex.Avalonia.Collections {
/// Tracks which pages have been fetched so far
private bool[] fetchedPages;
/// The wrapped list under its type-safe interface
- private IList typedList;
+ private TItem[] typedList;
/// The wrapped list under its object interface
private IList objectList;
#if DEBUG