diff --git a/sandbox/BlazorApp/BlazorApp.csproj b/sandbox/BlazorApp/BlazorApp.csproj index e948168..e5eafff 100644 --- a/sandbox/BlazorApp/BlazorApp.csproj +++ b/sandbox/BlazorApp/BlazorApp.csproj @@ -4,4 +4,8 @@ net6.0 + + + + diff --git a/sandbox/BlazorApp/Pages/Index.razor b/sandbox/BlazorApp/Pages/Index.razor index e54d914..1a6f6f4 100644 --- a/sandbox/BlazorApp/Pages/Index.razor +++ b/sandbox/BlazorApp/Pages/Index.razor @@ -1,7 +1,12 @@ @page "/" -

Hello, world!

+ -Welcome to your new app. - - + + @foreach (var item in ItemsView) + { + + + + } +
@item
\ No newline at end of file diff --git a/sandbox/BlazorApp/Pages/Index.razor.cs b/sandbox/BlazorApp/Pages/Index.razor.cs index c8a0fc1..45a68e3 100644 --- a/sandbox/BlazorApp/Pages/Index.razor.cs +++ b/sandbox/BlazorApp/Pages/Index.razor.cs @@ -1,9 +1,40 @@ using Microsoft.AspNetCore.Components; +using ObservableCollections; namespace BlazorApp.Pages; public partial class Index { + ObservableList list; + public ISynchronizedView ItemsView { get; set; } + int adder = 99; + RenderFragment fragment; + + protected override void OnInitialized() + { + list = new ObservableList(); + list.AddRange(new[] { 1, 10, 188 }); + ItemsView = list.CreateSortedView(x => x, x => x, comparer: Comparer.Default).WithINotifyCollectionChanged(); + + + fragment = builder => + { + builder.GetFrames(); + + }; + } + + + + void OnClick() + { + ThreadPool.QueueUserWorkItem(_ => + { + list.Add(adder++); + + _ = InvokeAsync(StateHasChanged); + }); + } } diff --git a/sandbox/WpfApp/MainWindow.xaml.cs b/sandbox/WpfApp/MainWindow.xaml.cs index 38ab9bf..835e88d 100644 --- a/sandbox/WpfApp/MainWindow.xaml.cs +++ b/sandbox/WpfApp/MainWindow.xaml.cs @@ -30,7 +30,7 @@ namespace WpfApp InitializeComponent(); this.DataContext = this; - + list = new ObservableList(); diff --git a/src/ObservableCollections/FreezedDictionary.cs b/src/ObservableCollections/FreezedDictionary.cs index 4a80455..c973850 100644 --- a/src/ObservableCollections/FreezedDictionary.cs +++ b/src/ObservableCollections/FreezedDictionary.cs @@ -1,4 +1,6 @@ -using ObservableCollections.Internal; +#nullable disable + +using ObservableCollections.Internal; using System; using System.Collections; using System.Collections.Generic; diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index d382fc8..ef21cac 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -1,4 +1,6 @@ using ObservableCollections.Internal; +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; diff --git a/src/ObservableCollections/Internal/CloneCollection.cs b/src/ObservableCollections/Internal/CloneCollection.cs index 7bfadc9..4e9268e 100644 --- a/src/ObservableCollections/Internal/CloneCollection.cs +++ b/src/ObservableCollections/Internal/CloneCollection.cs @@ -1,5 +1,7 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace ObservableCollections.Internal @@ -24,7 +26,7 @@ namespace ObservableCollections.Internal public CloneCollection(IEnumerable source) { - if (Enumerable.TryGetNonEnumeratedCount(source, out var count)) + if (source.TryGetNonEnumeratedCount(out var count)) { var array = ArrayPool.Shared.Rent(count); @@ -70,7 +72,7 @@ namespace ObservableCollections.Internal { if (array.Length == index) { - ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences()); + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); } array = ArrayPool.Shared.Rent(index * 2); } @@ -79,7 +81,7 @@ namespace ObservableCollections.Internal { if (array != null) { - ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences()); + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); array = null; } } diff --git a/src/ObservableCollections/Internal/ResizableArray.cs b/src/ObservableCollections/Internal/ResizableArray.cs index c520fab..a999db0 100644 --- a/src/ObservableCollections/Internal/ResizableArray.cs +++ b/src/ObservableCollections/Internal/ResizableArray.cs @@ -1,4 +1,5 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -32,7 +33,7 @@ namespace ObservableCollections.Internal void EnsureCapacity() { var newArray = array.AsSpan().ToArray(); - ArrayPool.Shared.Return(array!, RuntimeHelpers.IsReferenceOrContainsReferences()); + ArrayPool.Shared.Return(array!, RuntimeHelpersEx.IsReferenceOrContainsReferences()); array = newArray; } @@ -40,7 +41,7 @@ namespace ObservableCollections.Internal { if (array != null) { - ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences()); + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); array = null; } } diff --git a/src/ObservableCollections/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs index 7dec6d8..7aaa6c8 100644 --- a/src/ObservableCollections/Internal/SortedView.cs +++ b/src/ObservableCollections/Internal/SortedView.cs @@ -1,5 +1,8 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections.Internal { diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs index b479017..b8aad3e 100644 --- a/src/ObservableCollections/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections/Internal/SortedViewViewComparer.cs @@ -1,5 +1,8 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections.Internal { diff --git a/src/ObservableCollections/ObservableCollections.csproj b/src/ObservableCollections/ObservableCollections.csproj index 58ef59a..496abf6 100644 --- a/src/ObservableCollections/ObservableCollections.csproj +++ b/src/ObservableCollections/ObservableCollections.csproj @@ -1,8 +1,13 @@ - net6.0 + netstandard2.0;netstandard2.1;net5.0;net6.0 enable + 10.0 + + + + diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 256fe08..2bee71e 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -1,6 +1,9 @@ using ObservableCollections.Internal; +using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections { diff --git a/src/ObservableCollections/ObservableDictionary.cs b/src/ObservableCollections/ObservableDictionary.cs index 891988e..964d731 100644 --- a/src/ObservableCollections/ObservableDictionary.cs +++ b/src/ObservableCollections/ObservableDictionary.cs @@ -20,7 +20,15 @@ namespace ObservableCollections public ObservableDictionary(IEnumerable> collection) { +#if NET6_0_OR_GREATER this.dictionary = new Dictionary(collection); +#else + this.dictionary = new Dictionary(); + foreach (var item in collection) + { + dictionary.Add(item.Key, item.Value); + } +#endif } public event NotifyCollectionChangedEventHandler>? CollectionChanged; @@ -191,7 +199,9 @@ namespace ObservableCollections } } +#pragma warning disable CS8767 public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) +#pragma warning restore CS8767 { lock (SyncRoot) { diff --git a/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs b/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs index a450564..d67a11c 100644 --- a/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs +++ b/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs @@ -1,5 +1,7 @@ using ObservableCollections.Internal; +using System; using System.Collections; +using System.Collections.Generic; namespace ObservableCollections { diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index d362841..49450af 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -1,6 +1,9 @@ using ObservableCollections.Internal; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System; +using System.Linq; namespace ObservableCollections { diff --git a/src/ObservableCollections/ObservableHashSet.cs b/src/ObservableCollections/ObservableHashSet.cs index 736c6be..3183f0c 100644 --- a/src/ObservableCollections/ObservableHashSet.cs +++ b/src/ObservableCollections/ObservableHashSet.cs @@ -1,5 +1,7 @@ using ObservableCollections.Internal; +using System; using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace ObservableCollections @@ -16,11 +18,15 @@ namespace ObservableCollections this.set = new HashSet(); } +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + public ObservableHashSet(int capacity) { this.set = new HashSet(capacity); } +#endif + public ObservableHashSet(IEnumerable collection) { this.set = new HashSet(collection); @@ -174,11 +180,15 @@ namespace ObservableCollections } } +#if !NETSTANDARD2_0 + public bool TryGetValue(T equalValue, [MaybeNullWhen(false)] out T actualValue) { return set.TryGetValue(equalValue, out actualValue); } +#endif + public bool Contains(T item) { lock (SyncRoot) diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 49e463c..0a503b6 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -1,6 +1,9 @@ using ObservableCollections.Internal; +using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections { diff --git a/src/ObservableCollections/ObservableList.cs b/src/ObservableCollections/ObservableList.cs index 92ac0f8..f611db3 100644 --- a/src/ObservableCollections/ObservableList.cs +++ b/src/ObservableCollections/ObservableList.cs @@ -102,7 +102,6 @@ namespace ObservableCollections lock (SyncRoot) { var index = list.Count; - list.EnsureCapacity(items.Length); foreach (var item in items) { list.Add(item); @@ -242,7 +241,11 @@ namespace ObservableCollections { lock (SyncRoot) { +#if NET5_0_OR_GREATER var range = CollectionsMarshal.AsSpan(list).Slice(index, count); +#else + var range = list.GetRange(index, count); +#endif // require copy before remove using (var xs = new CloneCollection(range)) diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index dc2ef59..82fc720 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -1,6 +1,9 @@ using ObservableCollections.Internal; using System.Collections; using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Linq; namespace ObservableCollections { @@ -120,7 +123,6 @@ namespace ObservableCollections } else { - queue.EnsureCapacity(e.NewItems.Length); foreach (var item in e.NewItems) { var v = (item, selector(item)); diff --git a/src/ObservableCollections/ObservableQueue.cs b/src/ObservableCollections/ObservableQueue.cs index 6696a6a..7de4d5f 100644 --- a/src/ObservableCollections/ObservableQueue.cs +++ b/src/ObservableCollections/ObservableQueue.cs @@ -3,6 +3,10 @@ using System.Buffers; using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections { @@ -105,11 +109,13 @@ namespace ObservableCollections { lock (SyncRoot) { - if (queue.TryDequeue(out result)) + if (queue.Count != 0) { + result = queue.Dequeue(); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(result, 0)); return true; } + result = default; return false; } } @@ -130,7 +136,7 @@ namespace ObservableCollections } finally { - ArrayPool.Shared.Return(dest, RuntimeHelpers.IsReferenceOrContainsReferences()); + ArrayPool.Shared.Return(dest, RuntimeHelpersEx.IsReferenceOrContainsReferences()); } } } @@ -169,7 +175,13 @@ namespace ObservableCollections { lock (SyncRoot) { - return queue.TryPeek(out result); + if (queue.Count != 0) + { + result = queue.Peek(); + return true; + } + result = default; + return false; } } @@ -189,14 +201,6 @@ namespace ObservableCollections } } - public void EnsureCapacity(int capacity) - { - lock (SyncRoot) - { - queue.EnsureCapacity(capacity); - } - } - public IEnumerator GetEnumerator() { return new SynchronizedEnumerator(SyncRoot, queue.GetEnumerator()); diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index aa7fc94..50df81e 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -1,6 +1,9 @@ using ObservableCollections.Internal; +using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections { diff --git a/src/ObservableCollections/ObservableRingBuffer.cs b/src/ObservableCollections/ObservableRingBuffer.cs index c59ab83..0f653e3 100644 --- a/src/ObservableCollections/ObservableRingBuffer.cs +++ b/src/ObservableCollections/ObservableRingBuffer.cs @@ -1,4 +1,7 @@ using ObservableCollections.Internal; +using System; +using System.Linq; +using System.Collections.Generic; using System.Collections; namespace ObservableCollections diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index 3a3d4ef..81cbf97 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -1,6 +1,9 @@ using ObservableCollections.Internal; +using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; namespace ObservableCollections { @@ -120,7 +123,6 @@ namespace ObservableCollections } else { - stack.EnsureCapacity(e.NewItems.Length); foreach (var item in e.NewItems) { var v = (item, selector(item)); diff --git a/src/ObservableCollections/ObservableStack.cs b/src/ObservableCollections/ObservableStack.cs index 2de5999..e1d56e7 100644 --- a/src/ObservableCollections/ObservableStack.cs +++ b/src/ObservableCollections/ObservableStack.cs @@ -1,6 +1,8 @@ using ObservableCollections.Internal; +using System; using System.Buffers; using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -102,11 +104,14 @@ namespace ObservableCollections { lock (SyncRoot) { - if (stack.TryPop(out result)) + if (stack.Count != 0) { + result = stack.Pop(); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(result, 0)); return true; } + + result = default; return false; } } @@ -127,7 +132,7 @@ namespace ObservableCollections } finally { - ArrayPool.Shared.Return(dest, RuntimeHelpers.IsReferenceOrContainsReferences()); + ArrayPool.Shared.Return(dest, RuntimeHelpersEx.IsReferenceOrContainsReferences()); } } } @@ -166,7 +171,13 @@ namespace ObservableCollections { lock (SyncRoot) { - return stack.TryPeek(out result); + if (stack.Count != 0) + { + result = stack.Peek(); + return true; + } + result = default; + return false; } } @@ -186,14 +197,6 @@ namespace ObservableCollections } } - public void EnsureCapacity(int capacity) - { - lock (SyncRoot) - { - stack.EnsureCapacity(capacity); - } - } - public IEnumerator GetEnumerator() { return new SynchronizedEnumerator(SyncRoot, stack.GetEnumerator()); diff --git a/src/ObservableCollections/RingBuffer.cs b/src/ObservableCollections/RingBuffer.cs index 5ba6d0d..a4d24dd 100644 --- a/src/ObservableCollections/RingBuffer.cs +++ b/src/ObservableCollections/RingBuffer.cs @@ -1,4 +1,6 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace ObservableCollections @@ -147,7 +149,7 @@ namespace ObservableCollections public void Clear() { - Array.Clear(buffer); + Array.Clear(buffer, 0, buffer.Length); head = 0; count = 0; } diff --git a/src/ObservableCollections/Shims/Collections.cs b/src/ObservableCollections/Shims/Collections.cs new file mode 100644 index 0000000..d20c328 --- /dev/null +++ b/src/ObservableCollections/Shims/Collections.cs @@ -0,0 +1,64 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Collections.Generic +{ + internal static class CollectionExtensions + { + public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) + { + key = kvp.Key; + value = kvp.Value; + } + + public static bool Remove(this SortedDictionary dict, TKey key, out TValue value) + { + if (dict.TryGetValue(key, out value)) + { + return dict.Remove(key); + } + return false; + } + + public static bool Remove(this Dictionary dict, TKey key, out TValue value) + { + if (dict.TryGetValue(key, out value)) + { + return dict.Remove(key); + } + return false; + } + +#if !NET6_0_OR_GREATER + + public static bool TryGetNonEnumeratedCount(this IEnumerable source, out int count) + { + if (source is ICollection collection) + { + count = collection.Count; + return true; + } + if (source is IReadOnlyCollection rCollection) + { + count = rCollection.Count; + return true; + } + count = 0; + return false; + } + +#endif + } + +#if !NET5_0_OR_GREATER + + internal interface IReadOnlySet : System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection + { + } + +#endif +} + diff --git a/src/ObservableCollections/Shims/CompilerServices.cs b/src/ObservableCollections/Shims/CompilerServices.cs new file mode 100644 index 0000000..07826dc --- /dev/null +++ b/src/ObservableCollections/Shims/CompilerServices.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Runtime.CompilerServices +{ + internal static class RuntimeHelpersEx + { + internal static bool IsReferenceOrContainsReferences() + { +#if NETSTANDARD2_0 + return true; +#else + return RuntimeHelpers.IsReferenceOrContainsReferences(); +#endif + } + } +} diff --git a/src/ObservableCollections/Shims/Nullables.cs b/src/ObservableCollections/Shims/Nullables.cs new file mode 100644 index 0000000..3e96443 --- /dev/null +++ b/src/ObservableCollections/Shims/Nullables.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +#if NETSTANDARD2_0 + +namespace System.Diagnostics.CodeAnalysis +{ + internal sealed class MaybeNullWhenAttribute : Attribute + { + public MaybeNullWhenAttribute(bool returnValue) + { + } + } + + internal sealed class DoesNotReturnAttribute : Attribute + { + public DoesNotReturnAttribute() + { + } + } +} + +#endif \ No newline at end of file