From e92efc95493cfbd68910bd039044254aafda9993 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Sun, 5 Nov 2023 15:31:42 -0800 Subject: [PATCH 01/18] Use .Slice when getting a buffer from a memory pool. --- source/Extensions.Subsets.cs | 8 ++++---- source/Open.Collections.csproj | 2 +- testing/Open.Collections.Tests/SubsetTests.cs | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/source/Extensions.Subsets.cs b/source/Extensions.Subsets.cs index 456fa39..e267eff 100644 --- a/source/Extensions.Subsets.cs +++ b/source/Extensions.Subsets.cs @@ -84,7 +84,7 @@ static IEnumerable> SubsetsCore(IReadOnlyList source, int count, Me public static IEnumerable> SubsetsBuffered(this IReadOnlyList source, int count) { using var lease = MemoryPool.Shared.Rent(count); - Memory buffer = lease.Memory; + Memory buffer = lease.Memory.Slice(0, count); ReadOnlyMemory readBuffer = buffer; foreach (Memory _ in Subsets(source, count, buffer)) yield return readBuffer; @@ -99,7 +99,7 @@ public static IEnumerable> SubsetsBuffered(this IReadOnlyLi public static IEnumerable Subsets(this IReadOnlyList source, int count) { using var lease = MemoryPool.Shared.Rent(count); - Memory buffer = lease.Memory; + Memory buffer = lease.Memory.Slice(0, count); foreach (Memory _ in Subsets(source, count, buffer)) { var a = new T[count]; @@ -192,7 +192,7 @@ static IEnumerable> SubsetsCore(ReadOnlyMemory source, int count, M public static IEnumerable> SubsetsBuffered(this ReadOnlyMemory source, int count) { using var lease = MemoryPool.Shared.Rent(count); - Memory buffer = lease.Memory; + Memory buffer = lease.Memory.Slice(0, count); ReadOnlyMemory readBuffer = buffer; foreach (Memory _ in Subsets(source, count, buffer)) yield return readBuffer; @@ -202,7 +202,7 @@ public static IEnumerable> SubsetsBuffered(this ReadOnlyMem public static IEnumerable Subsets(this ReadOnlyMemory source, int count) { using var lease = MemoryPool.Shared.Rent(count); - Memory buffer = lease.Memory; + Memory buffer = lease.Memory.Slice(0, count); foreach (Memory _ in Subsets(source, count, buffer)) { var a = new T[count]; diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 51de950..a8b2137 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 3.1.4 + 3.1.5 MIT true diff --git a/testing/Open.Collections.Tests/SubsetTests.cs b/testing/Open.Collections.Tests/SubsetTests.cs index 5a74c0e..2681c17 100644 --- a/testing/Open.Collections.Tests/SubsetTests.cs +++ b/testing/Open.Collections.Tests/SubsetTests.cs @@ -1,3 +1,4 @@ +using System; using System.Buffers; using System.Collections.Immutable; using System.Linq; @@ -24,6 +25,9 @@ public void TestSubset1_2() int[][] actual = Set1.Subsets(2).ToArray(); Assert.Equal(expected, actual); + ReadOnlyMemory mem = Set1.ToArray(); + Assert.Equal(expected, mem.Subsets(2).ToArray()); + int[][] progressive = Set1.SubsetsProgressive(2).ToArray(); Assert.Equal(expected, progressive); } From 89807b4ffae4619cc9ba4a4e1be9cf45bb384f8a Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Wed, 22 Nov 2023 08:21:43 -0800 Subject: [PATCH 02/18] Extended permutations and improved peformance. --- source/Extensions.Permutations.cs | 279 +++++++++++++----- source/Extensions.cs | 48 ++- source/Open.Collections.csproj | 2 +- .../PermutationTests.cs | 28 +- .../Open.Collections.Tests/PermutorTests.cs | 140 +++++++++ 5 files changed, 422 insertions(+), 75 deletions(-) create mode 100644 testing/Open.Collections.Tests/PermutorTests.cs diff --git a/source/Extensions.Permutations.cs b/source/Extensions.Permutations.cs index 843bdc6..690689a 100644 --- a/source/Extensions.Permutations.cs +++ b/source/Extensions.Permutations.cs @@ -4,113 +4,252 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Xml.Linq; namespace Open.Collections; public static partial class Extensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static IEnumerable PermutationsCore(Action> appendElements, T[] buffer, int count) + /// + /// Uses Heap's algorithm to iterate through all possible permutations. + /// + public static IEnumerable> Permutations(this Memory set) { - if (count == 0) yield break; - if (count > buffer.Length) - throw new ArgumentOutOfRangeException(nameof(buffer), buffer, "Length is less than the number of elements."); + int n = set.Length; + if (n == 0) yield break; + yield return set; + if (n == 1) yield break; - // based on: https://stackoverflow.com/questions/1145703/permutation-of-string-without-recursion + var lease = MemoryPool.Shared.Rent(n); + var c = lease.Memory.Slice(0, n); + c.Span.Clear(); // Initialize counter array - int max = 1; - for (int i = 2; i <= count; i++) max *= i; - - int[]? a = new int[count]; - var pos = new LinkedList(); - - for (int j = 0; j < max; ++j) + int i = 0; + while (i < n) { - appendElements(pos); + int cValue = c.Span[i]; + if (cValue < i) + { + var span = set.Span; + if (i % 2 == 0) + Swap(ref span[0], ref span[i]); + else + Swap(ref span[cValue], ref span[i]); - int i; - int n = j; - int c = 0; + yield return set; - for (i = count; i > 0; --i) - { - int m = n; n /= i; - a[c++] = m % i; + c.Span[i] = cValue + 1; + i = 0; } - - // Avoid copy if not needed. - for (i = 0; i < count; i++) + else { - int index = a[i]; - var node = pos.First; - for (int ni = 0; ni < index; ni++) - node = node.Next; - buffer[i] = node.Value; - pos.Remove(node); + c.Span[i] = 0; + i++; } + } + } + + /// + public static IEnumerable> Permutations(this T[] set) + => set.AsMemory().Permutations(); + + /// + /// A fast, memory efficient way to get a unique permutation based upon an index of all possible permutations. + /// Modifies the source span to produce the results. Be sure to copy the source if you want to keep it. + /// + /// The set to permute. + /// The index of the permutation. + /// Is not in lexicographic order. + /// The permuted as a . + public static ReadOnlySpan Permutation(this Span source, BigInteger n) + { + if (n <= ulong.MaxValue) + { + return n < 0 + ? throw new ArgumentOutOfRangeException(nameof(n), n, "Must be at least zero.") + : Permutation(source, (ulong)n); + } + + int size = source.Length; + int i = 0, j; + for (; i < size; ++i) + { + if (n <= long.MaxValue) + return Permutation(source, (long)n, i); - yield return buffer; + j = i + 1; + int index = (int)(n % j); + if (index != i) + Swap(ref source[index], ref source[i]); + n /= j; } + + return source; } - static IEnumerable PermutationsCore(IReadOnlyCollection elements, T[] buffer) + /// + public static ReadOnlySpan Permutation(this Span source, ulong n) { - int count = elements.Count; - if (count == 0) return Enumerable.Empty(); - if (count > buffer.Length) - throw new ArgumentOutOfRangeException(nameof(buffer), buffer, "Length is less than the number of elements."); - Contract.EndContractBlock(); + int size = source.Length; + int i = 0; + uint j = 0; + for (; i < size; ++i) + { + if (n <= long.MaxValue) + return Permutation(source, (long)n, i); - return PermutationsCore(pos => pos.AddRange(elements), buffer, count); + int index = (int)(n % ++j); + if (index != i) + Swap(ref source[index], ref source[i]); + n /= j; + } + + return source; } - static IEnumerable PermutationsCore(ReadOnlyMemory elements, T[] buffer) + /// + public static ReadOnlySpan Permutation(this Span source, long n, int i = 0) { - int count = elements.Length; - if (count == 0) return Enumerable.Empty(); - if (count > buffer.Length) - throw new ArgumentOutOfRangeException(nameof(buffer), buffer, "Length is less than the number of elements."); - Contract.EndContractBlock(); + int size = source.Length; + for (int j; i < size; ++i) + { + if (n < int.MaxValue) + return Permutation(source, (int)n, i); + + j = i + 1; + int index = (int)(n % j); + if (index != i) + Swap(ref source[index], ref source[i]); + n /= j; + } - return PermutationsCore(pos => + return source; + } + + /// + public static ReadOnlySpan Permutation(this Span source, int n, int i = 0) + { + if (n < 0) throw new ArgumentOutOfRangeException(nameof(n), n, "Must be at least zero."); + + if (n == 0) return source; + n--; + + int size = source.Length; + for (int j; i < size; ++i) { - foreach (var e in elements.Span) - pos.AddLast(e); - }, buffer, count); + j = i + 1; + int index = n % j; + if (index != i) + Swap(ref source[index], ref source[i]); + n /= j; + } + + return source; + } + + /// + /// Permutes the to the next permutation. + /// + /// The sequence to permute. + /// + /// To reset the sequence after reaching the end (returns false) simply reverse the . + /// if there are more permutations in the sequence; otherwise . + /// + public static bool NextLexicographic(this Span span) + where T : IComparable + { + // 1. Find the largest index i such that array[i] < array[i + 1] + int i = span.Length - 2; + while (i >= 0 && span[i].CompareTo(span[i + 1]) >= 0) i--; + + // If no such index exists, the permutation is the last permutation + if (i < 0) + return false; + + // 2. Find the largest index j greater than i such that array[i] < array[j] + int j = span.Length - 1; + while (span[i].CompareTo(span[j]) >= 0) j--; + + // 3. Swap the values at array[i] and array[j] + Swap(ref span[i], ref span[j]); + + // 4. Reverse the sequence from array[i + 1] up to the last element + span.Slice(i + 1).Reverse(); + + return true; } + /// + public static bool NextLexicographic(this Span span) + { + // 1. Find the largest index i such that span[i] < span[i + 1] + int i = span.Length - 2; + while (i >= 0 && span[i] >= span[i + 1]) i--; + + // If no such index exists, the permutation is the last permutation + if (i < 0) + return false; + + // 2. Find the largest index j greater than i such that span[i] < span[j] + int j = span.Length - 1; + while (span[i] >= span[j]) j--; + + // 3. Swap the values at span[i] and span[j] + Swap(ref span[i], ref span[j]); + + // 4. Reverse the sequence from span[i + 1] up to the last element + span.Slice(i + 1).Reverse(); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Swap(ref T a, ref T b) + => (b, a) = (a, b); + /// The elements to draw from. - /// The buffer array that is filled with the values and returned as the yielded value instead of a new array + /// The buffer that is filled with the values and returned as the yielded value instead of a copy. /// - public static IEnumerable Permutations(this IReadOnlyCollection elements, T[] buffer) + public static IEnumerable> Permutations(this IReadOnlyCollection elements, Memory buffer) { if (elements is null) throw new ArgumentNullException(nameof(elements)); - if (buffer is null) throw new ArgumentNullException(nameof(buffer)); + + int count = elements.Count; + if (count == 0) return Enumerable.Empty>(); Contract.EndContractBlock(); - return PermutationsCore(elements, buffer); + var b = buffer.Slice(0, elements.Count); + elements.CopyToSpan(b.Span); + + return b.Permutations(); } /// The elements to draw from. - /// The buffer array that is filled with the values and returned as the yielded value instead of a new array + /// The buffer that is filled with the values and returned as the yielded value instead of a copy. /// - public static IEnumerable Permutations(this ReadOnlyMemory elements, T[] buffer) + public static IEnumerable> Permutations(this ReadOnlyMemory elements, Memory buffer) { - if (buffer is null) throw new ArgumentNullException(nameof(buffer)); + int count = elements.Length; + if (count == 0) return Enumerable.Empty>(); Contract.EndContractBlock(); - return PermutationsCore(elements, buffer); + var b = buffer.Slice(0, elements.Length); + elements.CopyTo(b); + + return b.Permutations(); } - /// - public static IEnumerable Permutations(this IEnumerable elements, T[] buffer) + /// + public static IEnumerable> Permutations(this IEnumerable elements, Memory buffer) { if (elements is null) throw new ArgumentNullException(nameof(elements)); - if (buffer is null) throw new ArgumentNullException(nameof(buffer)); Contract.EndContractBlock(); - return PermutationsCore(elements is IReadOnlyCollection c ? c : elements.ToArray(), buffer); + return elements is IReadOnlyCollection c + ? Permutations(c, buffer) + : Permutations(elements.ToArray().AsMemory(), buffer); } /// @@ -127,13 +266,15 @@ static IEnumerable> PermutationsBufferedCore(IReadOnlyCollecti int count = elements.Count; if (count == 0) yield break; + // We're using an ArrayPool here instead of a MemoryPool because benchmarking shows it can be slightly faster for smaller sizes. ArrayPool? pool = count > 128 ? ArrayPool.Shared : null; T[]? buffer = pool?.Rent(count) ?? new T[count]; - var readBuffer = new ReadOnlyMemory(buffer, 0, count); try { - foreach (T[]? _ in PermutationsCore(elements, buffer)) - yield return readBuffer; + var m = buffer.AsMemory().Slice(0, count); + elements.CopyToSpan(m.Span); + foreach (var b in m.Permutations()) + yield return b; } finally { @@ -148,13 +289,15 @@ public static IEnumerable> PermutationsBuffered(this ReadOn int count = elements.Length; if (count == 0) yield break; + // We're using an ArrayPool here instead of a MemoryPool because benchmarking shows it can be slightly faster for smaller sizes. ArrayPool? pool = count > 128 ? ArrayPool.Shared : null; T[]? buffer = pool?.Rent(count) ?? new T[count]; - var readBuffer = new ReadOnlyMemory(buffer, 0, count); try { - foreach (T[]? _ in PermutationsCore(elements, buffer)) - yield return readBuffer; + var m = buffer.AsMemory().Slice(0, count); + elements.CopyTo(m); + foreach (var b in Permutations(elements, buffer)) + yield return b; } finally { @@ -187,9 +330,9 @@ public static IEnumerable Permutations(this ReadOnlyMemory elements) } /// - /// Enumerates all possible unique permutations of a given set. + /// Using Heap's algorithm, enumerates all possible unique permutations of a given set. /// - /// [A, B, C] results in [A, B, C], [A, C, B], [B, A, C], [C, A, B], [B, C, A], [C, B, A] + /// [A, B, C] results in [A, B, C], [B, A, C], [C, A, B], [A, C, B], [B, C, A], [C, B, A] /// The elements to derive from. public static IEnumerable Permutations(this IEnumerable elements) { diff --git a/source/Extensions.cs b/source/Extensions.cs index b45d425..2a6263e 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -901,6 +901,33 @@ public static int IndexOf(this T[] source, T value) return -1; } + /// + /// Optimized method for getting a node by index + /// + public static LinkedListNode GetNodeAt(this LinkedList list, int index) + { + LinkedListNode current; + + // Determine whether to start from the beginning or the end. + int count = list.Count; + if (index < count / 2) + { + // Start from the beginning + current = list.First; + for (int i = 0; i < index; i++) + current = current.Next; + + return current; + } + + // Start from the end + current = list.Last; + for (int i = count - 1; i > index; i--) + current = current.Previous; + + return current; + } + /// /// Copies the results to the provided span up to its length or until the end of the results. /// @@ -913,17 +940,30 @@ public static int IndexOf(this T[] source, T value) /// public static Span CopyToSpan(this IEnumerable source, Span target) { - int len = target.Length; - if (len == 0) return target; + int tLen = target.Length; + if (tLen == 0) return target; + + if (source is T[] a) + { + int sLen = a.Length; + if (tLen < sLen) + { + a.AsSpan(0, tLen).CopyTo(target); + return target; + } + + a.CopyTo(target); + return tLen == sLen ? target : target.Slice(0, sLen); + } int count = 0; foreach (T? e in source) { target[count] = e; - if (len == ++count) return target; + if (tLen == ++count) return target; } - return target.Slice(0, count); + return tLen == count ? target : target.Slice(0, count); } /// diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index a8b2137..f76121b 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 3.1.5 + 3.2.0 MIT true diff --git a/testing/Open.Collections.Tests/PermutationTests.cs b/testing/Open.Collections.Tests/PermutationTests.cs index e910c7c..a301aea 100644 --- a/testing/Open.Collections.Tests/PermutationTests.cs +++ b/testing/Open.Collections.Tests/PermutationTests.cs @@ -1,4 +1,5 @@ -using System; +using FluentAssertions; +using System; using System.Collections.Immutable; using System.Linq; using Xunit; @@ -11,13 +12,36 @@ public class PermutationTests static readonly ReadOnlyMemory Set2 = new char[] { 'A', 'B', 'C' }; [Fact] - public void TestPermutation1() + public void TestPermutation1a() { int[][] expected = new int[][] { new int[] { 1, 2 }, new int[] { 2, 1 }, }; int[][] actual = Set1.Permutations().ToArray(); + actual.Length.Should().Be(2); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(12)] + [InlineData(24)] + public void TestPermutation1b(int bufferLength) + { + int[][] expected = new int[][] { + new int[] { 1, 2 }, + new int[] { 2, 1 }, + }; + + int[] buffer = new int[bufferLength]; + + int[][] actual = Set1.Permutations(buffer).Select(b => b.ToArray()).ToArray(); + actual.Length.Should().Be(2); Assert.Equal(expected, actual); } diff --git a/testing/Open.Collections.Tests/PermutorTests.cs b/testing/Open.Collections.Tests/PermutorTests.cs new file mode 100644 index 0000000..f9f3a16 --- /dev/null +++ b/testing/Open.Collections.Tests/PermutorTests.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using FluentAssertions; +using Xunit; + +namespace Open.Collections.Tests; + +public class PermutorTests +{ + [Fact] + public void TestNoDuplicatePermutations() + { + int[] numbers = { 1, 2, 3, 4 }; + var permutations = numbers.Permutations().Select(p => string.Join(",", p.Span.ToArray())).ToHashSet(); + + int expectedCount = Factorial(numbers.Length); + Assert.Equal(expectedCount, permutations.Count); + } + + [Fact] + public void TestSpecificPermutation() + { + int[] numbers = { 1, 2, 3 }; + var permutations = numbers.AsMemory().Permutations().Select(m=>m.ToArray()).ToList(); + permutations.Count.Should().Be(6); + int[] expectedPermutation = new[] { 2, 1, 3 }; + Assert.Contains(expectedPermutation, permutations); + } + + [Fact] + public void TestExactPermutationsFor123() + { + int[] numbers = { 1, 2, 3 }; + var expectedPermutations = new List + { + "1,2,3", + "2,1,3", + "3,1,2", + "1,3,2", + "2,3,1", + "3,2,1" + }; + + var permutations = numbers.AsMemory().Permutations() + .Select(p => string.Join(",", p.Span.ToArray())) + .ToList(); + + Assert.Equal(expectedPermutations.Count, permutations.Count); + foreach (string perm in expectedPermutations) + { + Assert.Contains(perm, permutations); + } + } + + [Fact] + public void TestStableLexicographicOrder() + { + int[] original = { 1, 2, 3 }; + Span span = original.AsSpan(); + + var permutations = new List(); + do + { + permutations.Add(string.Join(",", span.ToArray())); + } + while (span.NextLexicographic()); + + var expectedPermutations = new List + { + "1,2,3", + "1,3,2", + "2,1,3", + "2,3,1", + "3,1,2", + "3,2,1" + }; + + Assert.Equal(expectedPermutations, permutations); + } + + + [Fact] + public void TestStableHeapsAlgorithmOrder() + { + int[] original = { 1, 2, 3 }; + var permutations = new List(); + foreach(var s in original.Permutations()) + permutations.Add(string.Join(",", s.ToArray())); + + var expectedPermutations = new List + { + "1,2,3", + "2,1,3", + "3,1,2", + "1,3,2", + "2,3,1", + "3,2,1" + }; + + Assert.Equal(expectedPermutations, permutations); + } + + [Fact] + public void TestStableIndexedOrder() + { + int[] original = { 1, 2, 3 }; + var permutations = new List(); + for (int i = 0; i < 6; ++i) + { + string s = string.Join(",", original.ToArray().AsSpan().Permutation(i).ToArray()); + int c = permutations.IndexOf(s); + Assert.True(c==-1, $"{s} already exists in the set at index [{c}] of {permutations.Count}."); + permutations.Contains(s).Should().BeFalse(s + " "); + permutations.Add(s); + } + + var expectedPermutations = new List + { + "1,2,3", + "3,1,2", + "3,2,1", + "2,3,1", + "1,3,2", + "2,1,3", + }; + + Assert.Equal(expectedPermutations, permutations); + } + + + private static int Factorial(int n) + { + int result = 1; + for (int i = 2; i <= n; i++) + result *= i; + return result; + } +} From 3252306462ad490a406ea5cb26cd588c971f82f1 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:32:03 -0800 Subject: [PATCH 03/18] Refactor to use primary constructors and update targets Refactored multiple classes to use C# 9.0 primary constructors, simplifying initialization and improving readability. Updated project files to target .NET 9.0 and included new package references. Suppressed specific style warnings and added XML documentation to various classes. Enhanced thread-safety and nullability handling in several methods. Improved test classes with modern C# features and updated array initializations. --- .editorconfig | 10 +- Trie/ConcurrentTrie.cs | 19 +- Trie/Open.Collections.Trie.csproj | 5 +- Trie/StringJoinPool.cs | 20 +-- .../MaybeNullWhenAttribute.cs | 14 +- Trie/Trie.cs | 23 +-- Trie/TrieBase.cs | 27 +-- .../Benchmarks/CollectionBenchmark.cs | 8 +- .../Benchmarks/CollectionParallelBenchmark.cs | 17 +- .../Benchmarks/DictionaryParallelBenchmark.cs | 9 +- .../Benchmarks/LinkedListBenchmark.cs | 8 +- .../Benchmarks/LinkedListParallelBenchmark.cs | 8 +- .../Benchmarks/ListParallelBenchmark.cs | 9 +- benchmarking/Benchmarks/QueueBenchmark.cs | 7 +- .../Benchmarks/QueueParallelBenchmark.cs | 6 +- .../Benchmarks/SubsetBufferedBench.cs | 4 +- benchmarking/Benchmarks/TrieBenchmarks.cs | 2 +- .../Open.Collections.Benchmarking.csproj | 5 +- benchmarking/Program.cs | 3 + source/ArrayPoolSegment.cs | 5 +- source/CollectionWrapper.cs | 10 +- source/ConcurrentHashSet.cs | 7 + source/DictionaryToHashSetWrapper.cs | 11 +- source/DictionaryWrapper.cs | 8 +- source/DictionaryWrapperBase.cs | 37 +++- source/Extensions.Combinations.cs | 4 +- source/Extensions.ConcurrentDictionary.cs | 10 +- source/Extensions.Generic.Synchronized.cs | 45 +++-- source/Extensions.Generic.cs | 16 +- source/Extensions.Stream.cs | 12 +- source/Extensions.cs | 165 ++++++++++-------- source/IndexedDictionary.cs | 21 ++- source/ItemChangedEventArgs.cs | 25 ++- source/LazyList.cs | 35 ++-- source/LazyListUnsafe.cs | 51 +++--- source/LinkedList/ILinkedList.cs | 7 +- source/ListWrapper.cs | 12 +- source/Open.Collections.csproj | 18 +- source/OrderedDictionary.cs | 27 ++- source/Queue/IQueue.cs | 18 +- source/Queue/Queue.Standard.cs | 22 ++- source/ReadOnlyCollectionAdapter.cs | 18 +- source/ReadOnlyCollectionWrapper.cs | 1 + source/Synchronized/ConcurrentList.cs | 15 +- .../LockSynchronizedDictionaryWrapper.cs | 23 ++- .../Synchronized/LockSynchronizedHashSet.cs | 14 +- .../LockSynchronizedIndexedDictionary.cs | 10 +- .../LockSynchronizedLinkedList.cs | 4 +- source/Synchronized/LockSynchronizedList.cs | 16 +- .../LockSynchronizedListWrapper.cs | 15 +- .../LockSynchronizedOrderedDictionary.cs | 10 +- source/Synchronized/LockSynchronizedQueue.cs | 14 +- .../ReadWriteSynchronizedDictionaryWrapper.cs | 24 +-- .../ReadWriteSynchronizedHashSet.cs | 15 +- .../ReadWriteSynchronizedLinkedList.cs | 15 +- .../Synchronized/ReadWriteSynchronizedList.cs | 14 +- .../ReadWriteSynchronizedListWrapper.cs | 23 +-- .../Synchronized/TrackedDictionaryWrapper.cs | 50 +++++- .../TrackedIndexedDictionaryWrapper.cs | 3 + source/Synchronized/TrackedListWrapper.cs | 32 +++- .../BasicCollectionTests.cs | 14 +- .../BasicDictionaryTests.cs | 14 +- .../BasicLinkedListTests.cs | 5 +- .../Open.Collections.Tests/BasicListTests.cs | 10 +- .../Collections/ConcurrentListTests.cs | 2 +- .../Collections/LockSyncDictionaryTests.cs | 9 +- .../Collections/LockSyncLinkedListTests.cs | 3 +- .../Collections/LockSyncListTests.cs | 4 +- .../Collections/OrderedDictionaryTests.cs | 4 +- .../ReadWriteSyncLDictionaryTests.cs | 8 +- .../ReadWriteSyncLinkedListTests.cs | 2 +- .../Collections/ReadWriteSyncListTests.cs | 4 +- .../Collections/TrackedDictionaryTests.cs | 8 +- .../CombinationTests.cs | 102 +++++------ .../Open.Collections.Tests.csproj | 15 +- .../OrderedDictionaryTests.cs | 5 +- .../ParallelDictionaryTests.cs | 7 +- .../ParallelListTests.cs | 8 +- .../PermutationTests.cs | 34 ++-- .../Open.Collections.Tests/PermutorTests.cs | 16 +- testing/Open.Collections.Tests/SubsetTests.cs | 92 +++++----- testing/Open.Collections.Tests/TrieTests.cs | 6 +- 82 files changed, 840 insertions(+), 618 deletions(-) diff --git a/.editorconfig b/.editorconfig index 76e37ea..4ab8963 100644 --- a/.editorconfig +++ b/.editorconfig @@ -226,6 +226,8 @@ dotnet_diagnostic.IDE0057.severity = silent dotnet_diagnostic.CS1591.severity = suggestion csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion #dotnet_diagnostic.SA0001.severity = suggestion [*.{cs,vb}] dotnet_style_coalesce_expression = true:warning @@ -234,4 +236,10 @@ tab_width = 4 indent_size = 4 end_of_line = crlf dotnet_style_null_propagation = true:warning -indent_style = tab \ No newline at end of file +indent_style = tab +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_diagnostic.CA2007.severity = error \ No newline at end of file diff --git a/Trie/ConcurrentTrie.cs b/Trie/ConcurrentTrie.cs index c360d65..2f7d096 100644 --- a/Trie/ConcurrentTrie.cs +++ b/Trie/ConcurrentTrie.cs @@ -6,22 +6,13 @@ namespace Open.Collections; /// /// A generic Trie collection. /// -public sealed class ConcurrentTrie - : TrieBase +public sealed class ConcurrentTrie( + IEqualityComparer? equalityComparer = null) + : TrieBase(() => new Node(equalityComparer)) where TKey : notnull { - /// - /// Constructs a . - /// - public ConcurrentTrie(IEqualityComparer? equalityComparer = null) - : base(() => new Node(equalityComparer)) - { } - - private sealed class Node : NodeBase + private sealed class Node(IEqualityComparer? equalityComparer) : NodeBase { - public Node(IEqualityComparer? equalityComparer) - => _equalityComparer = equalityComparer; - private readonly object _valueSync = new(); protected override void SetValue(TValue value) @@ -31,7 +22,7 @@ protected override void SetValue(TValue value) private readonly object _childSync = new(); - private readonly IEqualityComparer? _equalityComparer; + private readonly IEqualityComparer? _equalityComparer = equalityComparer; private ConcurrentDictionary>? _children; protected override void UpdateRecent(TKey key, ITrieNode child) diff --git a/Trie/Open.Collections.Trie.csproj b/Trie/Open.Collections.Trie.csproj index ca0fdee..aa1f8a0 100644 --- a/Trie/Open.Collections.Trie.csproj +++ b/Trie/Open.Collections.Trie.csproj @@ -26,6 +26,7 @@ snupkg logo.png README.md + IDE0130; @@ -44,11 +45,11 @@ - + - + \ No newline at end of file diff --git a/Trie/StringJoinPool.cs b/Trie/StringJoinPool.cs index 1280e0a..1905883 100644 --- a/Trie/StringJoinPool.cs +++ b/Trie/StringJoinPool.cs @@ -12,22 +12,12 @@ namespace Open.Collections; /// /// Useful for (re)generating cache keys. /// -public class StringJoinPool +public class StringJoinPool( + ITrie pool, ReadOnlyMemory separator) { - private readonly ReadOnlyMemory _separator; - private readonly ITrie _pool; + private readonly ITrie _pool = pool ?? throw new ArgumentNullException(nameof(pool)); private StringBuilder? _reusableBuilder; - /// - /// Constructs a . - /// - /// If the supplied pool is null. - public StringJoinPool(ITrie pool, ReadOnlyMemory separator) - { - _pool = pool ?? throw new ArgumentNullException(nameof(pool)); - _separator = separator; - } - /// public StringJoinPool(ITrie pool, string? separator = null) : this(pool, separator is null ? ReadOnlyMemory.Empty : separator.AsMemory()) @@ -75,7 +65,7 @@ string Build(ReadOnlySpan segments) int len = segments.Length; try { - if (_separator.IsEmpty) + if (separator.IsEmpty) { for (int i = 0; i < len; i++) AppendSegment(sb, segments[i]); @@ -85,7 +75,7 @@ string Build(ReadOnlySpan segments) Debug.Assert(segments.Length != 0); AppendSegment(sb, segments[0]); - var sepSpan = _separator.Span; + var sepSpan = separator.Span; int sLen = sepSpan.Length; for (int i = 1; i < len; i++) diff --git a/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs b/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs index a8f0c0c..ce5e8ea 100644 --- a/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs +++ b/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs @@ -1,4 +1,5 @@ #if NETSTANDARD2_0 + namespace System.Diagnostics.CodeAnalysis; // Use a shim for simplicity. @@ -6,18 +7,15 @@ namespace System.Diagnostics.CodeAnalysis; /// /// Indicates that the output may be null even if the corresponding type disallows it. /// +/// +/// Constructs a . +/// [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class MaybeNullWhenAttribute : Attribute +internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute { - /// - /// Constructs a . - /// - public MaybeNullWhenAttribute(bool returnValue) - => ReturnValue = returnValue; - /// /// The return value condition. /// - public bool ReturnValue { get; } + public bool ReturnValue { get; } = returnValue; } #endif \ No newline at end of file diff --git a/Trie/Trie.cs b/Trie/Trie.cs index 9ac5a8b..393c47d 100644 --- a/Trie/Trie.cs +++ b/Trie/Trie.cs @@ -4,34 +4,25 @@ namespace Open.Collections; /// /// A generic Trie collection. /// -public sealed class Trie - : TrieBase +public sealed class Trie( + IEqualityComparer? equalityComparer = null) + : TrieBase(() => new Node(equalityComparer)) where TKey : notnull { - /// - /// Constructs a . - /// - public Trie(IEqualityComparer? equalityComparer = null) - : base(() => new Node(equalityComparer)) - { } - - private sealed class Node : NodeBase + private sealed class Node(IEqualityComparer? equalityComparer) + : NodeBase { - private readonly IEqualityComparer? _equalityComparer; private Dictionary>? _children; - public Node(IEqualityComparer? equalityComparer) - => _equalityComparer = equalityComparer; - public override ITrieNode GetOrAddChild(TKey key) { var children = _children; if (children is null) - Children = _children = children = _equalityComparer is null ? new() : new(_equalityComparer); + Children = _children = children = equalityComparer is null ? new() : new(equalityComparer); else if (TryGetChildFrom(children, key, out var c)) return c; - var child = new Node(_equalityComparer); + var child = new Node(equalityComparer); children[key] = child; return child; } diff --git a/Trie/TrieBase.cs b/Trie/TrieBase.cs index 77c5dd2..0e65568 100644 --- a/Trie/TrieBase.cs +++ b/Trie/TrieBase.cs @@ -221,34 +221,23 @@ internal abstract class NodeBase : ITrieNode { protected IDictionary>? Children; - private readonly struct ValueContainer + private readonly struct ValueContainer(bool isSet, TValue value) { - public ValueContainer(bool isSet, TValue value) - { - IsSet = isSet; - Value = value; - } - public ValueContainer(TValue value) : this(true, value) { } - public bool IsSet { get; } - public TValue Value { get; } + public bool IsSet { get; } = isSet; + public TValue Value { get; } = value; } private ValueContainer _value; - private readonly struct Recent + private readonly struct Recent( + bool exists, TKey key, ITrieNode child) { - public Recent(bool exists, TKey key, ITrieNode child) - { - Exists = exists; - Key = key; - Child = child; - } - public bool Exists { get; } - public TKey Key { get; } - public ITrieNode Child { get; } + public bool Exists { get; } = exists; + public TKey Key { get; } = key; + public ITrieNode Child { get; } = child; } // It's not uncommon to have a 'hot path' that will be requested frequently. diff --git a/benchmarking/Benchmarks/CollectionBenchmark.cs b/benchmarking/Benchmarks/CollectionBenchmark.cs index 184f2d2..3d002bf 100644 --- a/benchmarking/Benchmarks/CollectionBenchmark.cs +++ b/benchmarking/Benchmarks/CollectionBenchmark.cs @@ -72,13 +72,9 @@ protected override IEnumerable TestOnceInternal() } } -public class CollectionBenchmark : CollectionBenchmark +public class CollectionBenchmark( + uint size, uint repeat, Func> factory) : CollectionBenchmark(size, repeat, factory, _ => new object()) { - public CollectionBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory, _ => new object()) - { - } - public static TimedResult[] Results(uint size, uint repeat, Func> factory, Func itemFactory) => new CollectionBenchmark(size, repeat, factory, itemFactory).Result; diff --git a/benchmarking/Benchmarks/CollectionParallelBenchmark.cs b/benchmarking/Benchmarks/CollectionParallelBenchmark.cs index 67209b3..721e1c9 100644 --- a/benchmarking/Benchmarks/CollectionParallelBenchmark.cs +++ b/benchmarking/Benchmarks/CollectionParallelBenchmark.cs @@ -6,12 +6,10 @@ namespace Open.Collections; -public class CollectionParallelBenchmark : CollectionBenchmark +public class CollectionParallelBenchmark( + uint size, uint repeat, Func> factory, Func itemFactory) + : CollectionBenchmark(size, repeat, factory, itemFactory) { - public CollectionParallelBenchmark(uint size, uint repeat, Func> factory, Func itemFactory) : base(size, repeat, factory, itemFactory) - { - } - protected override IEnumerable TestOnceInternal() { ICollection c = Param(); @@ -115,13 +113,10 @@ protected override IEnumerable TestOnceInternal() } } -public class CollectionParallelBenchmark : CollectionParallelBenchmark +public class CollectionParallelBenchmark( + uint size, uint repeat, Func> factory) + : CollectionParallelBenchmark(size, repeat, factory, _ => new object()) { - public CollectionParallelBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory, _ => new object()) - { - } - public static TimedResult[] Results(uint size, uint repeat, Func> factory, Func itemFactory) => new CollectionParallelBenchmark(size, repeat, factory, itemFactory).Result; diff --git a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs index 9b94aff..f45e2f4 100644 --- a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs +++ b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs @@ -7,13 +7,10 @@ namespace Open.Collections; -public class DictionaryParallelBenchmark : CollectionParallelBenchmark> +public class DictionaryParallelBenchmark( + uint size, uint repeat, Func> factory) + : CollectionParallelBenchmark>(size, repeat, factory, i => new KeyValuePair(i, new object())) { - public DictionaryParallelBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory, i => new KeyValuePair(i, new object())) - { - } - protected override IEnumerable TestOnceInternal() { //foreach (TimedResult t in base.TestOnceInternal()) diff --git a/benchmarking/Benchmarks/LinkedListBenchmark.cs b/benchmarking/Benchmarks/LinkedListBenchmark.cs index fc5a0a8..c5c8655 100644 --- a/benchmarking/Benchmarks/LinkedListBenchmark.cs +++ b/benchmarking/Benchmarks/LinkedListBenchmark.cs @@ -4,12 +4,10 @@ namespace Open.Collections; -public class LinkedListBenchmark : BenchmarkBase>> +public class LinkedListBenchmark( + uint size, uint repeat, Func> factory) + : BenchmarkBase>>(size, repeat, factory) { - public LinkedListBenchmark(uint size, uint repeat, Func> factory) : base(size, repeat, factory) - { - } - protected readonly object _item = new(); protected override IEnumerable TestOnceInternal() diff --git a/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs b/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs index c29c025..ae5990b 100644 --- a/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs +++ b/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs @@ -5,12 +5,10 @@ namespace Open.Collections; -public class LinkedListParallelBenchmark : LinkedListBenchmark +public class LinkedListParallelBenchmark( + uint size, uint repeat, Func> factory) + : LinkedListBenchmark(size, repeat, factory) { - public LinkedListParallelBenchmark(uint size, uint repeat, Func> factory) : base(size, repeat, factory) - { - } - protected override IEnumerable TestOnceInternal() { ILinkedList c = Param(); diff --git a/benchmarking/Benchmarks/ListParallelBenchmark.cs b/benchmarking/Benchmarks/ListParallelBenchmark.cs index 9a8e061..3f17069 100644 --- a/benchmarking/Benchmarks/ListParallelBenchmark.cs +++ b/benchmarking/Benchmarks/ListParallelBenchmark.cs @@ -4,13 +4,10 @@ namespace Open.Collections; -public class ListParallelBenchmark : CollectionParallelBenchmark +public class ListParallelBenchmark( + uint size, uint repeat, Func> factory) + : CollectionParallelBenchmark(size, repeat, factory) { - public ListParallelBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory) - { - } - // Get/Set (mutating entry) operations have no benefit to synchronization and are inherently thread safe. //protected override IEnumerable TestOnceInternal() //{ diff --git a/benchmarking/Benchmarks/QueueBenchmark.cs b/benchmarking/Benchmarks/QueueBenchmark.cs index 0839c64..42a7f10 100644 --- a/benchmarking/Benchmarks/QueueBenchmark.cs +++ b/benchmarking/Benchmarks/QueueBenchmark.cs @@ -4,13 +4,8 @@ namespace Open.Collections; -public class QueueBenchmark : BenchmarkBase>> +public class QueueBenchmark(uint size, uint repeat, Func> queueFactory) : BenchmarkBase>>(size, repeat, queueFactory) { - public QueueBenchmark(uint size, uint repeat, Func> queueFactory) - : base(size, repeat, queueFactory) - { - } - protected readonly object _item = new(); protected override IEnumerable TestOnceInternal() diff --git a/benchmarking/Benchmarks/QueueParallelBenchmark.cs b/benchmarking/Benchmarks/QueueParallelBenchmark.cs index f9d23d3..dde600d 100644 --- a/benchmarking/Benchmarks/QueueParallelBenchmark.cs +++ b/benchmarking/Benchmarks/QueueParallelBenchmark.cs @@ -5,12 +5,8 @@ namespace Open.Collections; -public class QueueParallelBenchmark : QueueBenchmark +public class QueueParallelBenchmark(uint size, uint repeat, Func> factory) : QueueBenchmark(size, repeat, factory) { - public QueueParallelBenchmark(uint size, uint repeat, Func> factory) : base(size, repeat, factory) - { - } - protected override IEnumerable TestOnceInternal() { IQueue queue = Param(); diff --git a/benchmarking/Benchmarks/SubsetBufferedBench.cs b/benchmarking/Benchmarks/SubsetBufferedBench.cs index 81553f0..9ec3efb 100644 --- a/benchmarking/Benchmarks/SubsetBufferedBench.cs +++ b/benchmarking/Benchmarks/SubsetBufferedBench.cs @@ -22,7 +22,7 @@ namespace Open.Collections.Benchmarks; //[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")] public class SubsetBufferedBench { - IReadOnlyList FullSet = Array.Empty(); + int[] FullSet = Array.Empty(); ReadOnlyMemory FullMemorySet = ReadOnlyMemory.Empty; [Params(3, 7)] @@ -31,7 +31,7 @@ public class SubsetBufferedBench [Params(9, 32)] public int Range { - get => FullSet.Count; + get => FullSet.Length; set { int[] s = Enumerable.Range(0, value).ToArray(); diff --git a/benchmarking/Benchmarks/TrieBenchmarks.cs b/benchmarking/Benchmarks/TrieBenchmarks.cs index 881cfe1..4bfde35 100644 --- a/benchmarking/Benchmarks/TrieBenchmarks.cs +++ b/benchmarking/Benchmarks/TrieBenchmarks.cs @@ -35,7 +35,7 @@ public void GlobalSetup() { int x = 0; trie = new(); - dictionary = new(); + dictionary = []; ctrie = new(); cdictionary = new(); keys = GenerateTree(Depth, NodeSize) diff --git a/benchmarking/Open.Collections.Benchmarking.csproj b/benchmarking/Open.Collections.Benchmarking.csproj index cd1aa13..4e951ed 100644 --- a/benchmarking/Open.Collections.Benchmarking.csproj +++ b/benchmarking/Open.Collections.Benchmarking.csproj @@ -2,9 +2,10 @@ Exe - net7.0 + net9.0 Open.Collections latest + IDE0305;IDE0301;IDE0130; @@ -14,7 +15,7 @@ - + diff --git a/benchmarking/Program.cs b/benchmarking/Program.cs index 3e0d9c3..9e20caa 100644 --- a/benchmarking/Program.cs +++ b/benchmarking/Program.cs @@ -13,6 +13,7 @@ namespace Open.Collections.Benchmarks; [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1213:Remove unused member declaration.", Justification = "")] internal static class Program { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0022:Use expression body for method")] static void Main() { //BenchmarkRunner.Run(); @@ -141,6 +142,7 @@ static void LinkedListTests() // report.Test(1000); //} + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0028:Simplify collection initialization")] static void ListTests() { Console.WriteLine("::: Synchronized Lists :::\n"); @@ -164,6 +166,7 @@ static void ListTests() report.Test(4000, 4); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0028:Simplify collection initialization")] static void HashSetTests() { Console.WriteLine("::: Synchronized HashSets :::\n"); diff --git a/source/ArrayPoolSegment.cs b/source/ArrayPoolSegment.cs index 3c28cc4..74940e0 100644 --- a/source/ArrayPoolSegment.cs +++ b/source/ArrayPoolSegment.cs @@ -14,6 +14,9 @@ namespace Open.Collections; public readonly ArrayPool? Pool; private readonly bool _clear; + /// + /// Constructs a new from the . + /// public ArrayPoolSegment( int length, ArrayPool? pool = null, @@ -29,7 +32,7 @@ public ArrayPoolSegment( /// Returns the array to the pool. /// /// - public void Dispose() => Pool?.Return(Segment.Array, _clear); + public void Dispose() => Pool?.Return(Segment.Array!, _clear); /// /// Implicitly converts the to an . diff --git a/source/CollectionWrapper.cs b/source/CollectionWrapper.cs index ce35ff2..d67bf93 100644 --- a/source/CollectionWrapper.cs +++ b/source/CollectionWrapper.cs @@ -5,15 +5,11 @@ namespace Open.Collections; [ExcludeFromCodeCoverage] -public class CollectionWrapper - : ReadOnlyCollectionWrapper, ICollection, IAddMultiple +public class CollectionWrapper( + TCollection source, bool owner = false) + : ReadOnlyCollectionWrapper(source, owner), ICollection, IAddMultiple where TCollection : class, ICollection { - public CollectionWrapper(TCollection source, bool owner = false) - : base(source, owner) - { - } - protected readonly object Sync = new(); // Could possibly override.. /// diff --git a/source/ConcurrentHashSet.cs b/source/ConcurrentHashSet.cs index e55ab57..68cf492 100644 --- a/source/ConcurrentHashSet.cs +++ b/source/ConcurrentHashSet.cs @@ -4,9 +4,16 @@ namespace Open.Collections; +/// +/// A thread-safe hash by wrapping a . +/// [ExcludeFromCodeCoverage] public sealed class ConcurrentHashSet : DictionaryToHashSetWrapper + where T : notnull { + /// + /// Construct a new instance with optoinal initial values. + /// public ConcurrentHashSet(IEnumerable? intialValues = null) : base(new ConcurrentDictionary()) { diff --git a/source/DictionaryToHashSetWrapper.cs b/source/DictionaryToHashSetWrapper.cs index 6f7a81a..5c43dbf 100644 --- a/source/DictionaryToHashSetWrapper.cs +++ b/source/DictionaryToHashSetWrapper.cs @@ -5,13 +5,12 @@ namespace Open.Collections; -public class DictionaryToHashSetWrapper : ISet +[method: ExcludeFromCodeCoverage] +public class DictionaryToHashSetWrapper( + IDictionary source) + : ISet { - protected readonly IDictionary InternalSource; - - [ExcludeFromCodeCoverage] - public DictionaryToHashSetWrapper(IDictionary source) - => InternalSource = source; + protected readonly IDictionary InternalSource = source; /// [ExcludeFromCodeCoverage] diff --git a/source/DictionaryWrapper.cs b/source/DictionaryWrapper.cs index 0e9a6b8..086fc93 100644 --- a/source/DictionaryWrapper.cs +++ b/source/DictionaryWrapper.cs @@ -8,6 +8,7 @@ namespace Open.Collections; [ExcludeFromCodeCoverage] public class DictionaryWrapper : DictionaryWrapperBase> + where TKey : notnull { /// public DictionaryWrapper() @@ -61,6 +62,11 @@ public override bool Remove(TKey key) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool TryGetValue(TKey key, out TValue value) + public override bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#else +#endif + out TValue value) => InternalSource.TryGetValue(key, out value); } diff --git a/source/DictionaryWrapperBase.cs b/source/DictionaryWrapperBase.cs index a6e8273..3bc81b6 100644 --- a/source/DictionaryWrapperBase.cs +++ b/source/DictionaryWrapperBase.cs @@ -4,16 +4,16 @@ namespace Open.Collections; +/// +/// A base class for wrapping a collection as a dictionary. +/// [ExcludeFromCodeCoverage] -public abstract class DictionaryWrapperBase - : CollectionWrapper, TCollection>, IDictionary +public abstract class DictionaryWrapperBase( + TCollection source, bool owner = false) + : CollectionWrapper, TCollection>(source, owner), IDictionary + where TKey : notnull where TCollection : class, ICollection> { - protected DictionaryWrapperBase(TCollection source, bool owner = false) - : base(source, owner) - { - } - /// public TValue this[TKey key] { @@ -21,13 +21,23 @@ public TValue this[TKey key] set => SetValueInternal(key, value); } + /// + /// Get the value for the key. + /// protected abstract TValue GetValueInternal(TKey key); + /// + /// Set the value for the key. + /// protected abstract void SetValueInternal(TKey key, TValue value); ICollection? _keys; /// public ICollection Keys => _keys ??= GetKeys(); + + /// + /// Get the keys. + /// protected abstract ICollection GetKeys(); ICollection? _values; @@ -35,8 +45,14 @@ public TValue this[TKey key] /// public ICollection Values => _values ??= GetValues(); + /// + /// Get the values. + /// protected abstract ICollection GetValues(); + /// + /// Add a key and value to the dictionary. + /// protected abstract void AddInternal(TKey key, TValue value); /// @@ -51,5 +67,10 @@ public void Add(TKey key, TValue value) public abstract bool Remove(TKey key); /// - public abstract bool TryGetValue(TKey key, out TValue value); + public abstract bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#else +#endif + out TValue value); } diff --git a/source/Extensions.Combinations.cs b/source/Extensions.Combinations.cs index 57fa121..b738b44 100644 --- a/source/Extensions.Combinations.cs +++ b/source/Extensions.Combinations.cs @@ -160,13 +160,13 @@ public static IEnumerable Combinations(this IEnumerable elements, int Contract.EndContractBlock(); if (length == 0) return Enumerable.Empty(); - IReadOnlyList? source = elements as IReadOnlyList ?? elements.ToArray(); + IReadOnlyList source = elements as IReadOnlyList ?? elements.ToArray(); int count = source.Count; return count == 0 ? Enumerable.Empty() : uniqueOnly ? source.Subsets(length) - : CombinationsCore(source, length, true).Select(e => e.Array.AsCopy(length)); + : CombinationsCore(source, length, true).Select(e => e.Array!.ToArrayOfLength(length)); } /// diff --git a/source/Extensions.ConcurrentDictionary.cs b/source/Extensions.ConcurrentDictionary.cs index b0d1d7a..a3c347e 100644 --- a/source/Extensions.ConcurrentDictionary.cs +++ b/source/Extensions.ConcurrentDictionary.cs @@ -11,6 +11,7 @@ public static partial class Extensions /// Shortcut for removeing a value without needing an 'out' parameter. /// public static bool TryRemove(this ConcurrentDictionary target, TKey key) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); @@ -28,6 +29,7 @@ public static TValue GetOrAdd( out bool updated, TKey key, Func valueFactory) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -59,6 +61,7 @@ public static TValue GetOrAdd( out bool updated, TKey key, TValue value) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -81,7 +84,10 @@ public static TValue GetOrAdd( /// /// Will return true if the existing value is past due. /// - public static bool UpdateRequired(this ConcurrentDictionary source, TKey key, TimeSpan timeBeforeExpires) + public static bool UpdateRequired( + this ConcurrentDictionary source, TKey key, TimeSpan timeBeforeExpires) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -113,6 +119,7 @@ public static Lazy GetOrAddSafely( this ConcurrentDictionary> source, TKey key, Func valueFactory) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -144,6 +151,7 @@ public static Lazy> GetOrAddSafely( this ConcurrentDictionary>> source, TKey key, Func> valueFactory) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); diff --git a/source/Extensions.Generic.Synchronized.cs b/source/Extensions.Generic.Synchronized.cs index eade5b2..6c7bd9d 100644 --- a/source/Extensions.Generic.Synchronized.cs +++ b/source/Extensions.Generic.Synchronized.cs @@ -23,13 +23,18 @@ internal static void ValidateMillisecondsTimeout(int? millisecondsTimeout) /// True if a value was acquired. public static bool TryGetValueSynchronized( this IDictionary target, - TKey key, out TValue value) + TKey key, +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out TValue value) { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); - TValue result = default!; + TValue? result = default; bool success = ThreadSafety.SynchronizeRead(target, key, () => ThreadSafety.SynchronizeRead(target, () => target.TryGetValue(key, out result) @@ -44,8 +49,8 @@ public static bool TryGetValueSynchronized( /// /// Attempts to acquire a specified type from a generic dictonary. /// - [SuppressMessage("Style", "IDE0046:Convert to conditional expression")] - public static TValue GetValueSynchronized(this IDictionary target, TKey key, bool throwIfNotExists = true) + public static TValue GetValueSynchronized( + this IDictionary target, TKey key) { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -53,10 +58,22 @@ public static TValue GetValueSynchronized(this IDictionary + /// Attempts to acquire a specified type from a generic dictonary or returns a default value. + /// + public static TValue GetValueSynchronized( + this IDictionary target, TKey key, TValue defaultValue) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + if (key is null) throw new ArgumentNullException(nameof(key)); + Contract.EndContractBlock(); + + bool exists = target.TryGetValueSynchronized(key, out TValue? value); + + return exists ? value! : defaultValue; } /// @@ -159,6 +176,10 @@ public static T AddOrUpdateSynchronized(this IDictionary targe return valueUsed; } + /// + /// Thread safe shortcut for adding a value to list. + /// + /// public static void AddSynchronized(this ICollection target, T value) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -174,7 +195,7 @@ public static void AddToSynchronized(this IDictionary? list = c.GetOrAddSynchronized(key, _ => new List()); + IList? list = c.GetOrAddSynchronized(key, _ => []); list.AddSynchronized(value); } @@ -224,7 +245,7 @@ public static T GetOrAddSynchronized( ValidateMillisecondsTimeout(millisecondsTimeout); Contract.EndContractBlock(); - T result = default!; + T? result = default; bool condition(bool _) => !target.TryGetValue(key, out result); void render() @@ -236,7 +257,7 @@ void render() if (!ThreadSafety.SynchronizeReadWrite(target, condition, render, millisecondsTimeout, throwsOnTimeout)) return value; // Value doesn't exist and timeout exceeded? Return the add value... - return result; + return result!; } /// @@ -255,7 +276,7 @@ public static T GetOrAddSynchronized( ValidateMillisecondsTimeout(millisecondsTimeout); Contract.EndContractBlock(); - T result = default!; + T? result = default; // Note, the following sync read is on the TARGET and not the key. See below. bool condition(bool _) => !ThreadSafety.SynchronizeRead(target, () => target.TryGetValue(key, out result)); @@ -274,7 +295,7 @@ public static T GetOrAddSynchronized( // 5) The value is then rendered using the ensureRendered query without locking the entire collection. This allows for other values to be added. // 6) The rendered value is then used to add to the collection if the value is missing, locking the collection if an add is necessary. - return result; + return result!; } /// diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index a7b9a0c..e8cfaa1 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -38,7 +38,13 @@ public static void Register(this ICollection target, T value) target.Add(value); } - public static void AddRange(this ICollection target, IEnumerable values) + /// + /// Adds each value to the end of the collection. + /// + /// If the is null. + public static void AddRange( + this ICollection target, + IEnumerable values) { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); @@ -50,6 +56,9 @@ public static void AddRange(this ICollection target, IEnumerable values target.Add(value); } + /// + /// Adds each value to the end of the collection. + /// public static void AddThese(this ICollection target, T a, T b, params T[] more) { target.Add(a); @@ -58,6 +67,9 @@ public static void AddThese(this ICollection target, T a, T b, params T[] target.AddRange(more); } + /// + /// Removes each value from the collection. + /// public static int Remove(this ICollection target, IEnumerable values) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -156,7 +168,7 @@ public static void AddTo(this IDictionary> c, if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); - IList? list = c.GetOrAdd(key, _ => new List()); + IList? list = c.GetOrAdd(key, _ => []); list.Add(value); } diff --git a/source/Extensions.Stream.cs b/source/Extensions.Stream.cs index 57a75f8..dcd970f 100644 --- a/source/Extensions.Stream.cs +++ b/source/Extensions.Stream.cs @@ -11,10 +11,6 @@ public static partial class Extensions /// /// Copies the source stream to the target. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Reliability", - "CA2016:Forward the 'CancellationToken' parameter to methods", - Justification = "Is required to ensure recieved data is not lost.")] public static async ValueTask DualBufferCopyToAsync( this Stream source, Stream target, @@ -38,11 +34,11 @@ public static async ValueTask DualBufferCopyToAsync( if (n == 0) break; // Preemptive request before yielding. - Task? current = cancellationToken.IsCancellationRequested ? null : source.ReadAsync(cCurrent, 0, bufferSize); -#if NETSTANDARD2_1_OR_GREATER - await target.WriteAsync(cNext.AsMemory(0, n)).ConfigureAwait(false); + Task current = source.ReadAsync(cCurrent, 0, bufferSize, cancellationToken); +#if NETSTANDARD2_0 + await target.WriteAsync(cNext, 0, n, cancellationToken).ConfigureAwait(false); #else - await target.WriteAsync(cNext, 0, n).ConfigureAwait(false); + await target.WriteAsync(cNext.AsMemory(0, n), cancellationToken).ConfigureAwait(false); #endif if (current is null) throw new OperationCanceledException(); (cCurrent, cNext) = (cNext, cCurrent); diff --git a/source/Extensions.cs b/source/Extensions.cs index 2a6263e..e70ce0c 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Dynamic; using System.Linq; @@ -31,8 +32,8 @@ public static ExpandoObject ToExpando(this IEnumerable)expando; + ExpandoObject expando = new(); + var expandoDic = (IDictionary)expando!; // go through the items in the dictionary and copy over the key value pairs) @@ -75,6 +76,10 @@ public static ExpandoObject ToExpando(this IEnumerable + /// Clones the source 2D array into a new 2D array. + /// + /// The is null. public static T[,] BiClone(this T[,] source) { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -90,6 +95,10 @@ public static ExpandoObject ToExpando(this IEnumerable + /// Overwrites the target 2D array with the source 2D array. + /// + /// The or is null. public static void Overwrite(this T[,] source, T[,] target) { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -99,6 +108,10 @@ public static void Overwrite(this T[,] source, T[,] target) source.ForEach((x, y, value) => target[x, y] = value); } + /// + /// Iterates through the 2D array and executes the closure for each element. + /// + /// The or is null. public static void ForEach(this T[,] source, Action closure) { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -117,45 +130,35 @@ public static void ForEach(this T[,] source, Action closure) } } - public static T[] AsCopy(this T[] source, int? length = null) + /// + /// Creates a copy of the source array with a new length. + /// + /// If the is less than the length, the copy will contain all elements up to that length. If is greater then there will be untouched elements after that. + /// The is null. + public static T[] ToArrayOfLength(this T[] source, int length) { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); - var newArray = new T[length ?? source.Length]; + var newArray = new T[length]; int len = Math.Min(newArray.Length, source.Length); for (int i = 0; i < len; i++) newArray[i] = source[i]; return newArray; } - public static IEnumerable> CopyEachUsing( - this IEnumerable> source, - ArrayPool pool) - { - if (source is null) throw new ArgumentNullException(nameof(source)); - if (pool is null) throw new ArgumentNullException(nameof(pool)); - Contract.EndContractBlock(); - - return CopyUsingCore(source, pool); - - static IEnumerable> CopyUsingCore(IEnumerable> source, ArrayPool pool) - { - foreach (ReadOnlyMemory item in source) - { - int len = item.Length; - T[]? a = pool.Rent(len); - item.CopyTo(a); - yield return new ArraySegment(a, 0, len); - } - } - } - - public static ICollection AsCollection(this IEnumerable source) + /// + /// Coerces to a collection either by matching the type or by creating a new array. + /// + public static ICollection ToCollection(this IEnumerable source) => source is null ? null! : source as ICollection ?? source.ToArray(); + /// + /// Iterates over the source in parallel. + /// + /// The or are null. public static void ForEach(this IEnumerable target, ParallelOptions? parallelOptions, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -175,13 +178,21 @@ public static void ForEach(this IEnumerable target, ParallelOptions? paral closure); } - public static void ForEach(this IEnumerable target, Action closure, ushort parallel) + /// + /// Iterates over the source in parallel. + /// + /// The or are null. + public static void ForEach(this IEnumerable target, Action closure, ushort maxConcurrency) => target.ForEach( - parallel == 0 + maxConcurrency == 0 ? null - : new ParallelOptions { MaxDegreeOfParallelism = parallel }, + : new ParallelOptions { MaxDegreeOfParallelism = maxConcurrency }, closure); + /// + /// Iterates over the source and optionally can do so in parallel. + /// + /// The or are null. public static void ForEach(this IEnumerable target, Action closure, bool allowParallel = false) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -200,6 +211,10 @@ public static void ForEach(this IEnumerable target, Action closure, boo closure(t); } + /// + /// Iterates over the source in parallel with a lock. + /// + /// The or are null. public static void ForEach(this ISynchronizedCollection target, ParallelOptions? parallelOptions, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -222,13 +237,21 @@ public static void ForEach(this ISynchronizedCollection target, ParallelOp }); } - public static void ForEach(this ISynchronizedCollection target, Action closure, ushort parallel) + /// + /// Iterates over the source in parallel with a lock. + /// + /// The or are null. + public static void ForEach(this ISynchronizedCollection target, Action closure, ushort maxConcurrency) => target.ForEach( - parallel == 0 + maxConcurrency == 0 ? null - : new ParallelOptions { MaxDegreeOfParallelism = parallel }, + : new ParallelOptions { MaxDegreeOfParallelism = maxConcurrency }, closure); + /// + /// Iterates over the source with a lock and optionally can do so in parallel. + /// + /// The or are null. public static void ForEach(this ISynchronizedCollection target, Action closure, bool allowParallel = false) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -250,7 +273,13 @@ public static void ForEach(this ISynchronizedCollection target, Action }); } + /// + /// Iterates over the source and can be cancelled. + /// + /// The or are null. +#pragma warning disable IDE0079 // Remove unnecessary suppression [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Allows for simpler implementation. Other methods cover non-cancellable case.")] +#pragma warning restore IDE0079 // Remove unnecessary suppression public static void ForEach(this IEnumerable target, CancellationToken token, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -264,7 +293,10 @@ public static void ForEach(this IEnumerable target, CancellationToken toke } } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Allows for simpler implementation. Other methods cover non-cancellable case.")] + /// + /// Iterates over the source with a lock and can be cancelled. + /// + /// The or are null. public static void ForEach(this ISynchronizedCollection target, CancellationToken token, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -349,7 +381,7 @@ public static bool ConcurrentMoveNext(this IEnumerator source, Action t return false; } - static async Task PreCacheWorker(IEnumerator e, Channel queue) + static async Task PreCacheWorker(IEnumerator e, Channel queue, CancellationToken cancellationToken) { try { @@ -361,7 +393,7 @@ static async Task PreCacheWorker(IEnumerator e, Channel queue) retry: if (queue.Writer.TryWrite(value)) continue; - if (await queue.Writer.WaitToWriteAsync()) goto retry; + if (await queue.Writer.WaitToWriteAsync(cancellationToken).ConfigureAwait(false)) goto retry; break; } @@ -378,14 +410,14 @@ static async Task PreCacheWorker(IEnumerator e, Channel queue) /// /// Similar to a buffer but is loaded by another thread and attempts keep the buffer full while contents are being pulled. /// - public static IEnumerable PreCache(this IEnumerable source, int count = 1) + public static IEnumerable PreCache(this IEnumerable source, int count = 1, CancellationToken cancellationToken = default) { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); - return PreCacheCore(source, count); + return PreCacheCore(source, count, cancellationToken); - static IEnumerable PreCacheCore(IEnumerable source, int count) + static IEnumerable PreCacheCore(IEnumerable source, int count, CancellationToken cancellationToken) { if (count <= 0) { @@ -402,7 +434,7 @@ static IEnumerable PreCacheCore(IEnumerable source, int count) else yield break; // Queue up. - PreCacheWorker(e, queue).ConfigureAwait(false); + PreCacheWorker(e, queue, cancellationToken).ConfigureAwait(false); // Dequeue into the enumerable. do @@ -410,12 +442,12 @@ static IEnumerable PreCacheCore(IEnumerable source, int count) while (queue.Reader.TryRead(out T? item)) yield return item; } - while (queue.Reader.WaitToReadAsync().AsTask().Result); + while (queue.Reader.WaitToReadAsync(cancellationToken).AsTask().Result); - Task? complete = queue.Reader.Completion; + Task complete = queue.Reader.Completion; if (complete.IsFaulted) { - throw complete.Exception.InnerException; + throw complete.Exception.InnerException ?? complete.Exception; } } } @@ -517,6 +549,7 @@ public static string JoinToString(this IEnumerable source, string separato }*/ public static Dictionary ToDictionary(this ParallelQuery> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -525,6 +558,7 @@ public static Dictionary ToDictionary(this ParallelQ } public static Dictionary ToDictionary(this IEnumerable> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -533,6 +567,7 @@ public static Dictionary ToDictionary(this IEnumerab } public static SortedDictionary ToSortedDictionary(this IEnumerable> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -546,6 +581,7 @@ public static SortedDictionary ToSortedDictionary(th public static SortedDictionary ToSortedDictionary(this IEnumerable source, Func keySelector, Func valueSelector) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); if (keySelector is null) throw new ArgumentNullException(nameof(keySelector)); @@ -560,6 +596,7 @@ public static SortedDictionary ToSortedDictionary> ToSortedDictionary(this IEnumerable> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -649,7 +686,7 @@ public static string[] ToStringArray(this IEnumerable list) if (list is null) throw new ArgumentNullException(nameof(list)); Contract.EndContractBlock(); - return list.Select(r => r!.ToString()).ToArray(); + return list.Select(r => r!.ToString() ?? "null").ToArray(); } /// @@ -748,8 +785,9 @@ private static IQueryable ApplyOrderBy(IQueryable collection, OrderByIn object? result = r1 .MakeGenericMethod(typeof(T), type) - .Invoke(null, new object[] { collection, lambda }); + .Invoke(null, [collection, lambda]); + Debug.Assert(result is not null); return (IOrderedQueryable)result; } @@ -913,17 +951,17 @@ public static LinkedListNode GetNodeAt(this LinkedList list, int index) if (index < count / 2) { // Start from the beginning - current = list.First; + current = list.First!; for (int i = 0; i < index; i++) - current = current.Next; + current = current.Next!; return current; } // Start from the end - current = list.Last; + current = list.Last!; for (int i = count - 1; i > index; i--) - current = current.Previous; + current = current.Previous!; return current; } @@ -969,22 +1007,11 @@ public static Span CopyToSpan(this IEnumerable source, Span target) /// /// Builds an immutable array using the contents of the span. /// - public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) - { - ImmutableArray.Builder? builder = ImmutableArray.CreateBuilder(span.Length); - foreach (T? e in span) - builder.Add(e); - return builder.MoveToImmutable(); - } + public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) => [.. span]; /// public static ImmutableArray ToImmutableArray(this Span span) - { - ImmutableArray.Builder? builder = ImmutableArray.CreateBuilder(span.Length); - foreach (T? e in span) - builder.Add(e); - return builder.MoveToImmutable(); - } + => [.. span]; /// /// Builds an immutable array using the contents of the memory. @@ -1045,17 +1072,15 @@ public static IEnumerable BeforeGetEnumerator( Action before) => new PreflightEnumerable(source, before); - private class PreflightEnumerable : IEnumerable + private class PreflightEnumerable( + IEnumerable source, Action before) + : IEnumerable { - private readonly IEnumerable _source; - private readonly Action _before; + private readonly IEnumerable _source = source ?? throw new ArgumentNullException(nameof(source)); - public PreflightEnumerable(IEnumerable source, Action before) - { - _source = source ?? throw new ArgumentNullException(nameof(source)); - _before = before ?? throw new ArgumentNullException(nameof(before)); - } + private readonly Action _before = before ?? throw new ArgumentNullException(nameof(before)); + /// public IEnumerator GetEnumerator() { _before(); diff --git a/source/IndexedDictionary.cs b/source/IndexedDictionary.cs index bbeb2c9..24dec00 100644 --- a/source/IndexedDictionary.cs +++ b/source/IndexedDictionary.cs @@ -12,11 +12,15 @@ namespace Open.Collections; /// public class IndexedDictionary : DictionaryWrapper, IIndexedDictionary + where TKey : notnull { private const string OUTOFSYNC = "Collection is out of sync possibly due to unsynchronized access by multiple threads."; private readonly List> _entries; private readonly Dictionary _indexes; + /// + /// Constructs a new instance with the specified capacity. + /// [ExcludeFromCodeCoverage] public IndexedDictionary(int capacity) : base(capacity) @@ -25,13 +29,16 @@ public IndexedDictionary(int capacity) _indexes = new(capacity); } + /// + /// Constructs a new instance. + /// public IndexedDictionary() - : base() { - _entries = new(); - _indexes = new(); + _entries = []; + _indexes = []; } + /// protected override void OnDispose() { base.OnDispose(); @@ -39,6 +46,9 @@ protected override void OnDispose() _indexes.Clear(); } + /// + /// Sets the value for the key. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void SetValueInternal(TKey key, TValue value) => SetValue(key, value); @@ -46,12 +56,14 @@ protected override void OnDispose() public override IEnumerator> GetEnumerator() => _entries.GetEnumerator().Preflight(ThrowIfDisposedDelegate); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override ICollection GetKeys() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(_entries.Select(e => e.Key)), () => _entries.Count); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override ICollection GetValues() => new ReadOnlyCollectionAdapter( @@ -82,6 +94,7 @@ private int AddToLists(in KeyValuePair kvp) return i; } + /// [ExcludeFromCodeCoverage] [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(in KeyValuePair item) @@ -98,6 +111,7 @@ protected override void AddInternal(in KeyValuePair item) return AddToLists(in kvp); } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(TKey key, TValue value) => Add(key, value); @@ -157,6 +171,7 @@ private void RemoveIndex(int index, TKey key) Debug.Assert(_entries.Count == InternalSource.Count); } + /// public override void Clear() { base.Clear(); diff --git a/source/ItemChangedEventArgs.cs b/source/ItemChangedEventArgs.cs index cf3981c..0e2455e 100644 --- a/source/ItemChangedEventArgs.cs +++ b/source/ItemChangedEventArgs.cs @@ -14,25 +14,20 @@ public enum ItemChange Modified } -public class ItemChangedEventArgs : EventArgs +public class ItemChangedEventArgs( + ItemChange action, T value, int version) + : EventArgs { - public readonly ItemChange Change; - public readonly T Value; - public readonly int Version; - - public ItemChangedEventArgs(ItemChange action, T value, int version) - { - Change = action; - Value = value; - Version = version; - } + public readonly ItemChange Change = action; + public readonly T Value = value; + public readonly int Version = version; } -public class ItemChangedEventArgs : ItemChangedEventArgs +public class ItemChangedEventArgs( + ItemChange action, TIndex index, TValue value, int version) + : ItemChangedEventArgs(action, value, version) { - public readonly TIndex Index; - public ItemChangedEventArgs(ItemChange action, TIndex index, TValue value, int version) - : base(action, value, version) => Index = index; + public readonly TIndex Index = index; } public static class ItemChangeEventArgs diff --git a/source/LazyList.cs b/source/LazyList.cs index 0835796..4441323 100644 --- a/source/LazyList.cs +++ b/source/LazyList.cs @@ -15,23 +15,20 @@ namespace Open.Collections; /// A a thread-safe list for caching the results of an enumerable. /// Note: should be disposed manually whenever possible as the locking mechanism is a ReaderWriterLockSlim. /// -public sealed class LazyList : LazyListUnsafe +public sealed class LazyList( + IEnumerable source, bool isEndless = false) + : LazyListUnsafe(source) { - ReaderWriterLockSlim Sync; + ReaderWriterLockSlim Sync = new(LockRecursionPolicy.NoRecursion); int _safeCount; /// /// A value indicating whether the results are known or expected to be finite. /// A list that was constructed as endless but has reached the end of the results will return false. /// - public bool IsEndless { get; private set; } - - public LazyList(IEnumerable source, bool isEndless = false) : base(source) - { - Sync = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); // This is important as it's possible to recurse infinitely to generate a result. :( - IsEndless = isEndless; // To indicate if a source is not allowed to fully enumerate. - } + public bool IsEndless { get; private set; } = isEndless; // To indicate if a source is not allowed to fully enumerate. + /// protected override void OnDispose() { using (Sync.WriteLock()) base.OnDispose(); @@ -51,6 +48,7 @@ public override int IndexOf(T item) return base.IndexOf(item); } + /// protected override bool EnsureIndex(int maxIndex) { if (maxIndex < _safeCount) @@ -59,7 +57,7 @@ protected override bool EnsureIndex(int maxIndex) // This is where the fun begins... // Mutliple threads can be out of sync (probably through a memory barrier) // And a sync read operation must be done to ensure safety. - int count = Sync.Read(() => _cached.Count); + int count = Sync.Read(() => Cached.Count); if (maxIndex < count) { // We're still within the existing results, but safe count is not up to date. @@ -70,42 +68,43 @@ protected override bool EnsureIndex(int maxIndex) return true; } - if (_enumerator is null) + if (Enumerator is null) return false; // This very well could be a simple lock{} statement but the ReaderWriterLockSlim recursion protection is actually quite useful. using var uLock = Sync.UpgradableReadLock(); // Note: Within an upgradable read, other reads pile up. - int c = _cached.Count; + int c = Cached.Count; if (_safeCount != c) // Always do comparisons outside of interlocking first. Interlocked.CompareExchange(ref _safeCount, c, _safeCount); if (maxIndex < _safeCount) return true; - if (_enumerator is null) + if (Enumerator is null) return false; using var wLock = Sync.WriteLock(); - while (_enumerator.MoveNext()) + while (Enumerator.MoveNext()) { - if (_cached.Count == int.MaxValue) + if (Cached.Count == int.MaxValue) throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); - _cached.Add(_enumerator.Current); + Cached.Add(Enumerator.Current); - if (maxIndex < _cached.Count) + if (maxIndex < Cached.Count) return true; } IsEndless = false; - DisposeOf(ref _enumerator); + DisposeOf(ref Enumerator); return false; } + /// protected override void Finish() { if (IsEndless) diff --git a/source/LazyListUnsafe.cs b/source/LazyListUnsafe.cs index e8f1449..a532da1 100644 --- a/source/LazyListUnsafe.cs +++ b/source/LazyListUnsafe.cs @@ -16,21 +16,24 @@ namespace Open.Collections; /// Only use if you know the results are finite and access is thread safe. /// Note: disposing releases the underlying enumerator if it never reached the end of the results. /// -public class LazyListUnsafe : DisposableBase, IReadOnlyList +public class LazyListUnsafe(IEnumerable source) + : DisposableBase, IReadOnlyList { - protected List _cached; - protected IEnumerator _enumerator; + /// + /// The memoized results. + /// + protected List Cached = []; - public LazyListUnsafe(IEnumerable source) - { - _enumerator = source.GetEnumerator(); - _cached = new List(); - } + /// + /// The enumerator for the source. + /// + protected IEnumerator Enumerator = source.GetEnumerator(); + /// protected override void OnDispose() { - DisposeOf(ref _enumerator); - Nullify(ref _cached)?.Clear(); + DisposeOf(ref Enumerator); + Nullify(ref Cached)?.Clear(); } const string MUST_BE_AT_LEAST_ZERO = "Must be at least zero."; @@ -49,7 +52,7 @@ public T this[int index] throw new ArgumentOutOfRangeException(nameof(index), GREATER_THAN_TOTAL); Contract.EndContractBlock(); - return _cached[index]; + return Cached[index]; } } @@ -60,7 +63,7 @@ public int Count { AssertIsAlive(); Finish(); - return _cached.Count; + return Cached.Count; } } @@ -77,7 +80,7 @@ public bool TryGetValueAt(int index, out T value) if (EnsureIndex(index)) { - value = _cached[index]; + value = Cached[index]; return true; } @@ -103,7 +106,7 @@ public virtual int IndexOf(T item) for (int index = 0; EnsureIndex(index); index++) { - T? value = _cached[index]; + T? value = Cached[index]; if (value is null) { if (item is null) return index; @@ -137,30 +140,36 @@ public Span CopyTo(T[] array, int startIndex = 0) System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Ensures that the index is available by enumerating to that index and memoizing results. + /// protected virtual bool EnsureIndex(int maxIndex) { - if (maxIndex < _cached.Count) + if (maxIndex < Cached.Count) return true; - if (_enumerator is null) + if (Enumerator is null) return false; - while (_enumerator.MoveNext()) + while (Enumerator.MoveNext()) { - if (_cached.Count == int.MaxValue) + if (Cached.Count == int.MaxValue) throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); - _cached.Add(_enumerator.Current); + Cached.Add(Enumerator.Current); - if (maxIndex < _cached.Count) + if (maxIndex < Cached.Count) return true; } - DisposeOf(ref _enumerator); + DisposeOf(ref Enumerator); return false; } + /// + /// Ensures all results are memoized. + /// protected virtual void Finish() { while (EnsureIndex(int.MaxValue)) { } diff --git a/source/LinkedList/ILinkedList.cs b/source/LinkedList/ILinkedList.cs index 8753ada..3ca042d 100644 --- a/source/LinkedList/ILinkedList.cs +++ b/source/LinkedList/ILinkedList.cs @@ -2,13 +2,16 @@ namespace Open.Collections; +/// +/// An interface for a linked list. +/// public interface ILinkedList : ICollection { /// - LinkedListNode First { get; } + LinkedListNode? First { get; } /// - LinkedListNode Last { get; } + LinkedListNode? Last { get; } /// LinkedListNode AddAfter(LinkedListNode node, T item); diff --git a/source/ListWrapper.cs b/source/ListWrapper.cs index c465d07..fb5dacd 100644 --- a/source/ListWrapper.cs +++ b/source/ListWrapper.cs @@ -3,16 +3,12 @@ using System.Runtime.CompilerServices; namespace Open.Collections; -public class ListWrapper - : CollectionWrapper, IList + +public class ListWrapper( + TList source, bool owner = false) + : CollectionWrapper(source, owner), IList where TList : class, IList { - [ExcludeFromCodeCoverage] - public ListWrapper(TList source, bool owner = false) - : base(source, owner) - { - } - /// [ExcludeFromCodeCoverage] public virtual T this[int index] diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index f76121b..33efcb4 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net9.0 latest enable true @@ -26,10 +26,11 @@ snupkg logo.png README.md + IDE0130;CA1510;CA1068;IDE0305;IDE0301; - + @@ -50,13 +51,18 @@ - - - + + + - + + + + $(NoWarn);nullable + + \ No newline at end of file diff --git a/source/OrderedDictionary.cs b/source/OrderedDictionary.cs index fc47464..6c5ea93 100644 --- a/source/OrderedDictionary.cs +++ b/source/OrderedDictionary.cs @@ -7,14 +7,19 @@ namespace Open.Collections; +/// +/// A dictionary that maintains the order of items as they are added +/// and can be accessed by index. +/// public class OrderedDictionary : DictionaryWrapperBase>>, IDictionary + where TKey : notnull { /// [ExcludeFromCodeCoverage] public OrderedDictionary() : base(new LinkedList>(), true) - => _lookup = new(); + => _lookup = []; /// [ExcludeFromCodeCoverage] @@ -23,9 +28,14 @@ public OrderedDictionary(int capacity) => _lookup = new(capacity); private Dictionary>> _lookup; + + /// + /// The dictionary that can look up the node for a key. + /// protected Dictionary>> Lookup => _lookup ?? throw new ObjectDisposedException(GetType().ToString()); + /// [ExcludeFromCodeCoverage] protected override void OnDispose() { @@ -34,17 +44,22 @@ protected override void OnDispose() base.OnDispose(); } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override TValue GetValueInternal(TKey key) => Lookup[key].Value.Value; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void SetValueInternal(TKey key, TValue value) => SetValue(key, value); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(in KeyValuePair kvp) => AddNode(in kvp); - /// + /// + /// Updates a value by key and returns true if the value changed. + /// public virtual bool SetValue(TKey key, TValue value) { AssertIsAlive(); @@ -61,11 +76,13 @@ public virtual bool SetValue(TKey key, TValue value) return true; } + /// protected override ICollection GetKeys() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(InternalSource.Select(e => e.Key)), () => InternalSource.Count); + /// protected override ICollection GetValues() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(InternalSource.Select(e => e.Value)), @@ -76,6 +93,9 @@ protected override ICollection GetValues() protected override void AddInternal(TKey key, TValue value) => AddInternal(KeyValuePair.Create(key, value)); + /// + /// Adds a node to the lookup dictionary. + /// #if NETSTANDARD2_0 [SuppressMessage("Roslynator", "RCS1242:Do not pass non-read-only struct by read-only reference.", Justification = "KeyValuePairs are not truly readonly until NET Standard 2.1.")] #endif @@ -129,13 +149,14 @@ public override void Clear() base.Clear(); } + /// public override bool Remove(KeyValuePair item) { var key = item.Key; if (Lookup.TryGetValue(key, out var node) && (node.Value.Value?.Equals(item.Value) ?? item.Value is null)) { - Debug.Assert(key?.Equals(node.Value.Key) ?? node.Value.Key is null); + Debug.Assert(key.Equals(node.Value.Key)); bool removed = Lookup.Remove(key); Debug.Assert(removed); InternalSource.Remove(node); diff --git a/source/Queue/IQueue.cs b/source/Queue/IQueue.cs index 742208a..2b1f57c 100644 --- a/source/Queue/IQueue.cs +++ b/source/Queue/IQueue.cs @@ -1,4 +1,6 @@ -namespace Open.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace Open.Collections; public interface IQueue { @@ -6,10 +8,20 @@ public interface IQueue void Enqueue(T item); /// - bool TryDequeue(out T item); + bool TryDequeue( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item); /// - bool TryPeek(out T item); + bool TryPeek( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item); /// int Count { get; } diff --git a/source/Queue/Queue.Standard.cs b/source/Queue/Queue.Standard.cs index 8dd7554..50a11ac 100644 --- a/source/Queue/Queue.Standard.cs +++ b/source/Queue/Queue.Standard.cs @@ -3,15 +3,27 @@ namespace Open.Collections; +/// +/// Static collection of queue implementations. +/// public static partial class Queue { + /// + /// A standard queue implementation that is based upon . + /// public class Standard : Queue, IQueue { + /// + /// Construct an empty queue. + /// [ExcludeFromCodeCoverage] - protected Standard() + public Standard() { } + /// + /// Construct a queue with an initial set of items. + /// [ExcludeFromCodeCoverage] public Standard(IEnumerable initial) : base(initial) { @@ -38,12 +50,16 @@ public virtual bool TryPeek(out T item) /// [ExcludeFromCodeCoverage] - public new virtual bool TryDequeue(out T item) + public new virtual bool TryDequeue( + [MaybeNullWhen(false)] + out T item) => base.TryDequeue(out item); /// [ExcludeFromCodeCoverage] - public new virtual bool TryPeek(out T item) + public new virtual bool TryPeek( + [MaybeNullWhen(false)] + out T item) => base.TryPeek(out item); #endif diff --git a/source/ReadOnlyCollectionAdapter.cs b/source/ReadOnlyCollectionAdapter.cs index 5413294..739345f 100644 --- a/source/ReadOnlyCollectionAdapter.cs +++ b/source/ReadOnlyCollectionAdapter.cs @@ -7,22 +7,16 @@ namespace Open.Collections; -public sealed class ReadOnlyCollectionAdapter +[method: ExcludeFromCodeCoverage] +public sealed class ReadOnlyCollectionAdapter( + IEnumerable source, Func getCount) : IReadOnlyCollection, ICollection { - readonly IEnumerable _source; - readonly Func _getCount; - readonly Func _contains; - - [ExcludeFromCodeCoverage] - public ReadOnlyCollectionAdapter(IEnumerable source, Func getCount) - { - _source = source ?? throw new ArgumentNullException(nameof(source)); - _getCount = getCount ?? throw new ArgumentNullException(nameof(getCount)); - _contains = source is ICollection c + readonly IEnumerable _source = source ?? throw new ArgumentNullException(nameof(source)); + readonly Func _getCount = getCount ?? throw new ArgumentNullException(nameof(getCount)); + readonly Func _contains = source is ICollection c ? item => c.Contains(item) : item => source.Contains(item); - } [ExcludeFromCodeCoverage] public ReadOnlyCollectionAdapter(IReadOnlyCollection source) diff --git a/source/ReadOnlyCollectionWrapper.cs b/source/ReadOnlyCollectionWrapper.cs index 412c83f..9503e1c 100644 --- a/source/ReadOnlyCollectionWrapper.cs +++ b/source/ReadOnlyCollectionWrapper.cs @@ -27,6 +27,7 @@ protected TCollection InternalSource /// /// If the is . [ExcludeFromCodeCoverage] + [SuppressMessage("Style", "IDE0290:Use primary constructor")] public ReadOnlyCollectionWrapper(TCollection source, bool owner = false) { InternalUnsafeSource = source ?? throw new ArgumentNullException(nameof(source)); diff --git a/source/Synchronized/ConcurrentList.cs b/source/Synchronized/ConcurrentList.cs index fcf865b..8051e81 100644 --- a/source/Synchronized/ConcurrentList.cs +++ b/source/Synchronized/ConcurrentList.cs @@ -16,6 +16,8 @@ namespace Open.Collections.Synchronized; public sealed class ConcurrentList : ListWrapper>, ISynchronizedCollection { int _count; + + /// [ExcludeFromCodeCoverage] public override int Count { @@ -29,6 +31,7 @@ public override int Count private readonly Queue.Concurrent _buffer = new(); private readonly ReaderWriterLockSlim RWLock = new(); + /// [ExcludeFromCodeCoverage] protected override void OnDispose() { @@ -74,6 +77,9 @@ private List Grow() return list; } + /// + /// Gets or sets the capacity of the list. + /// public int Capacity { get => InternalSource.Capacity; @@ -84,11 +90,17 @@ public int Capacity } } + /// + /// Constructs a new instance with the specified capacity. + /// [ExcludeFromCodeCoverage] public ConcurrentList(int capacity) : base(new List(capacity)) { } + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] - public ConcurrentList() : base(new List()) { } + public ConcurrentList() : base([]) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AssertValidIndex(int index) @@ -111,6 +123,7 @@ public override T this[int index] } } + /// protected override void AddInternal(in T item) { _buffer.Enqueue(item); diff --git a/source/Synchronized/LockSynchronizedDictionaryWrapper.cs b/source/Synchronized/LockSynchronizedDictionaryWrapper.cs index ef44733..7f4d809 100644 --- a/source/Synchronized/LockSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/LockSynchronizedDictionaryWrapper.cs @@ -4,15 +4,11 @@ namespace Open.Collections.Synchronized; -/// [ExcludeFromCodeCoverage] -public class LockSynchronizedDictionaryWrapper - : LockSynchronizedCollectionWrapper, TDictionary>, IDictionary +public class LockSynchronizedDictionaryWrapper(TDictionary dictionary) + : LockSynchronizedCollectionWrapper, TDictionary>(dictionary), IDictionary where TDictionary : class, IDictionary { - /// - public LockSynchronizedDictionaryWrapper(TDictionary dictionary) : base(dictionary) { } - /// public virtual TValue this[TKey key] { @@ -64,22 +60,25 @@ public virtual bool Remove(TKey key) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(TKey key, out TValue value) + public bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#endif + out TValue value) => InternalSource.TryGetValue(key, out value); } [ExcludeFromCodeCoverage] -public class LockSynchronizedDictionaryWrapper - : LockSynchronizedDictionaryWrapper> +public class LockSynchronizedDictionaryWrapper( + IDictionary dictionary) + : LockSynchronizedDictionaryWrapper>(dictionary) { - public LockSynchronizedDictionaryWrapper(IDictionary dictionary) : base(dictionary) - { - } } [ExcludeFromCodeCoverage] public class LockSynchronizedDictionary : LockSynchronizedDictionaryWrapper + where TKey : notnull { public LockSynchronizedDictionary(int capacity) : base(new Dictionary(capacity)) { } public LockSynchronizedDictionary() : base(new Dictionary()) { } diff --git a/source/Synchronized/LockSynchronizedHashSet.cs b/source/Synchronized/LockSynchronizedHashSet.cs index 956c6c7..b1d413b 100644 --- a/source/Synchronized/LockSynchronizedHashSet.cs +++ b/source/Synchronized/LockSynchronizedHashSet.cs @@ -4,14 +4,26 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized . +/// public sealed class LockSynchronizedHashSet : LockSynchronizedCollectionWrapper>, ISet { + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] - public LockSynchronizedHashSet() : base(new HashSet()) { } + public LockSynchronizedHashSet() : base([]) { } + /// + /// Constructs a new instance with the specified capacity. + /// [ExcludeFromCodeCoverage] public LockSynchronizedHashSet(IEnumerable collection) : base(new HashSet(collection)) { } + /// + /// Constructs a new instance with the specified capacity and comparer. + /// [ExcludeFromCodeCoverage] public LockSynchronizedHashSet(IEnumerable collection, IEqualityComparer comparer) : base(new HashSet(collection, comparer)) { } diff --git a/source/Synchronized/LockSynchronizedIndexedDictionary.cs b/source/Synchronized/LockSynchronizedIndexedDictionary.cs index cec792a..551f59b 100644 --- a/source/Synchronized/LockSynchronizedIndexedDictionary.cs +++ b/source/Synchronized/LockSynchronizedIndexedDictionary.cs @@ -2,15 +2,11 @@ namespace Open.Collections.Synchronized; -/// [ExcludeFromCodeCoverage] // Nothing worth covering here yet. -public sealed class LockSynchronizedIndexedDictionary - : LockSynchronizedDictionaryWrapper>, IIndexedDictionary +public sealed class LockSynchronizedIndexedDictionary(int capacity = 0) + : LockSynchronizedDictionaryWrapper>(new IndexedDictionary(capacity)), IIndexedDictionary + where TKey : notnull { - /// - public LockSynchronizedIndexedDictionary(int capacity = 0) - : base(new IndexedDictionary(capacity)) { } - /// public TKey GetKeyAt(int index) => InternalSource.GetKeyAt(index); diff --git a/source/Synchronized/LockSynchronizedLinkedList.cs b/source/Synchronized/LockSynchronizedLinkedList.cs index 9ef7751..c5f7d77 100644 --- a/source/Synchronized/LockSynchronizedLinkedList.cs +++ b/source/Synchronized/LockSynchronizedLinkedList.cs @@ -15,12 +15,12 @@ public LockSynchronizedLinkedList(IEnumerable collection) : base(new LinkedLi /// [ExcludeFromCodeCoverage] - public LinkedListNode First + public LinkedListNode? First => InternalSource.First; /// [ExcludeFromCodeCoverage] - public LinkedListNode Last + public LinkedListNode? Last => InternalSource.Last; /// diff --git a/source/Synchronized/LockSynchronizedList.cs b/source/Synchronized/LockSynchronizedList.cs index 55dfd58..1172a5f 100644 --- a/source/Synchronized/LockSynchronizedList.cs +++ b/source/Synchronized/LockSynchronizedList.cs @@ -3,11 +3,25 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized list. +/// [ExcludeFromCodeCoverage] public sealed class LockSynchronizedList : LockSynchronizedListWrapper { - public LockSynchronizedList() : base(new List()) { } + /// + /// Constructs a new instance. + /// + public LockSynchronizedList() : base([]) { } + + /// + /// Constructs a new instance with the specified capacity. + /// public LockSynchronizedList(int capacity = 0) : base(new List(capacity)) { } + + /// + /// Constructs a new instance with the specified collection. + /// public LockSynchronizedList(IEnumerable collection) : base(new List(collection)) { } } diff --git a/source/Synchronized/LockSynchronizedListWrapper.cs b/source/Synchronized/LockSynchronizedListWrapper.cs index 5334df8..33d0d15 100644 --- a/source/Synchronized/LockSynchronizedListWrapper.cs +++ b/source/Synchronized/LockSynchronizedListWrapper.cs @@ -4,12 +4,11 @@ namespace Open.Collections.Synchronized; [ExcludeFromCodeCoverage] -public class LockSynchronizedListWrapper - : LockSynchronizedCollectionWrapper, IList +public class LockSynchronizedListWrapper( + TList list, bool owner = false) + : LockSynchronizedCollectionWrapper(list, owner), IList where TList : class, IList { - public LockSynchronizedListWrapper(TList list, bool owner = false) : base(list, owner) { } - // This is a simplified version. // It could be possible to allow indexed values to change independently of one another. // If that fine grained of read-write control is necessary, then use the ThreadSafety utility and extensions. @@ -41,10 +40,8 @@ public void RemoveAt(int index) } [ExcludeFromCodeCoverage] -public class LockSynchronizedListWrapper - : LockSynchronizedListWrapper> +public class LockSynchronizedListWrapper( + IList list, bool owner = false) + : LockSynchronizedListWrapper>(list, owner) { - public LockSynchronizedListWrapper(IList list, bool owner = false) : base(list, owner) - { - } } \ No newline at end of file diff --git a/source/Synchronized/LockSynchronizedOrderedDictionary.cs b/source/Synchronized/LockSynchronizedOrderedDictionary.cs index 10c2e3b..668e092 100644 --- a/source/Synchronized/LockSynchronizedOrderedDictionary.cs +++ b/source/Synchronized/LockSynchronizedOrderedDictionary.cs @@ -2,12 +2,10 @@ namespace Open.Collections.Synchronized; -/// [ExcludeFromCodeCoverage] -public sealed class LockSynchronizedOrderedDictionary - : LockSynchronizedDictionaryWrapper> +public sealed class LockSynchronizedOrderedDictionary( + int capacity = 0) + : LockSynchronizedDictionaryWrapper>(new OrderedDictionary(capacity)) + where TKey : notnull { - /// - public LockSynchronizedOrderedDictionary(int capacity = 0) - : base(new OrderedDictionary(capacity)) { } } diff --git a/source/Synchronized/LockSynchronizedQueue.cs b/source/Synchronized/LockSynchronizedQueue.cs index 4d95d2e..af6c4eb 100644 --- a/source/Synchronized/LockSynchronizedQueue.cs +++ b/source/Synchronized/LockSynchronizedQueue.cs @@ -17,7 +17,12 @@ public class LockSynchronizedQueue : Queue.Standard, IQueue } /// - public override bool TryDequeue(out T item) + public override bool TryDequeue( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item) { if (Count == 0) { @@ -30,7 +35,12 @@ public override bool TryDequeue(out T item) } /// - public override bool TryPeek(out T item) + public override bool TryPeek( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item) { if (Count == 0) { diff --git a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs index 44c1aff..f0f55f5 100644 --- a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs @@ -6,14 +6,11 @@ namespace Open.Collections.Synchronized; -/// -public class ReadWriteSynchronizedDictionaryWrapper - : ReadWriteSynchronizedCollectionWrapper, TDictionary>, IDictionary +public class ReadWriteSynchronizedDictionaryWrapper( + TDictionary dictionary, bool owner = false) + : ReadWriteSynchronizedCollectionWrapper, TDictionary>(dictionary, owner), IDictionary where TDictionary : class, IDictionary { - /// - public ReadWriteSynchronizedDictionaryWrapper(TDictionary dictionary, bool owner = false) : base(dictionary, owner) { } - /// [ExcludeFromCodeCoverage] public virtual TValue this[TKey key] @@ -79,7 +76,11 @@ public virtual bool Remove(TKey key) /// [ExcludeFromCodeCoverage] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(TKey key, out TValue value) + public bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#endif + out TValue value) => InternalSource.TryGetValue(key, out value); /// @@ -104,17 +105,16 @@ public virtual bool IfNotContainsKey(TKey key, Action> } [ExcludeFromCodeCoverage] -public class ReadWriteSynchronizedDictionaryWrapper - : ReadWriteSynchronizedDictionaryWrapper> +public class ReadWriteSynchronizedDictionaryWrapper( + IDictionary dictionary, bool owner = false) + : ReadWriteSynchronizedDictionaryWrapper>(dictionary, owner) { - public ReadWriteSynchronizedDictionaryWrapper(IDictionary dictionary, bool owner = false) : base(dictionary, owner) - { - } } [ExcludeFromCodeCoverage] public class ReadWriteSynchronizedDictionary : ReadWriteSynchronizedDictionaryWrapper + where TKey : notnull { public ReadWriteSynchronizedDictionary() : base(new Dictionary()) { } diff --git a/source/Synchronized/ReadWriteSynchronizedHashSet.cs b/source/Synchronized/ReadWriteSynchronizedHashSet.cs index 8aa4f55..6c60127 100644 --- a/source/Synchronized/ReadWriteSynchronizedHashSet.cs +++ b/source/Synchronized/ReadWriteSynchronizedHashSet.cs @@ -6,15 +6,28 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized that uses a for thread safety. +/// public sealed class ReadWriteSynchronizedHashSet : ReadWriteSynchronizedCollectionWrapper>, ISet { + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] - public ReadWriteSynchronizedHashSet() : base(new HashSet()) { } + public ReadWriteSynchronizedHashSet() : base([]) { } + /// + /// Constructs a new instance with the specified capacity. + /// + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedHashSet(IEnumerable collection) : base(new HashSet(collection)) { } + /// + /// Constructs a new instance with the specified capacity and comparer. + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedHashSet(IEnumerable collection, IEqualityComparer comparer) : base(new HashSet(collection, comparer)) { } diff --git a/source/Synchronized/ReadWriteSynchronizedLinkedList.cs b/source/Synchronized/ReadWriteSynchronizedLinkedList.cs index 3377598..12c590b 100644 --- a/source/Synchronized/ReadWriteSynchronizedLinkedList.cs +++ b/source/Synchronized/ReadWriteSynchronizedLinkedList.cs @@ -4,25 +4,36 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized wrapper for that uses a for thread safety. +/// +/// public sealed class ReadWriteSynchronizedLinkedList : ReadWriteSynchronizedCollectionWrapper>, ILinkedList { + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedLinkedList() : base(new LinkedList()) { } + /// + /// Constructs a new instance with the specified collection. + /// + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedLinkedList(IEnumerable collection) : base(new LinkedList(collection)) { } /// [ExcludeFromCodeCoverage] - public LinkedListNode First + public LinkedListNode? First => InternalSource.First; /// [ExcludeFromCodeCoverage] - public LinkedListNode Last + public LinkedListNode? Last => InternalSource.Last; /// diff --git a/source/Synchronized/ReadWriteSynchronizedList.cs b/source/Synchronized/ReadWriteSynchronizedList.cs index e7a832e..0c70fc6 100644 --- a/source/Synchronized/ReadWriteSynchronizedList.cs +++ b/source/Synchronized/ReadWriteSynchronizedList.cs @@ -3,16 +3,28 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized that uses a for thread safety. +/// [ExcludeFromCodeCoverage] public sealed class ReadWriteSynchronizedList : ReadWriteSynchronizedListWrapper { + /// + /// Constructs a new instance. + /// public ReadWriteSynchronizedList() - : base(new List()) { } + : base([]) { } + /// + /// Constructs a new instance with the specified capacity. + /// public ReadWriteSynchronizedList(int capacity = 0) : base(new List(capacity)) { } + /// + /// Constructs a new instance with the specified collection. + /// public ReadWriteSynchronizedList(IEnumerable collection) : base(new List(collection)) { } } diff --git a/source/Synchronized/ReadWriteSynchronizedListWrapper.cs b/source/Synchronized/ReadWriteSynchronizedListWrapper.cs index 6b649cf..2ac52ea 100644 --- a/source/Synchronized/ReadWriteSynchronizedListWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedListWrapper.cs @@ -4,13 +4,14 @@ namespace Open.Collections.Synchronized; -public class ReadWriteSynchronizedListWrapper - : ReadWriteSynchronizedCollectionWrapper, IList +/// +/// A synchronized wrapper for a list that uses a for synchronization. +/// +public class ReadWriteSynchronizedListWrapper( + TList list, bool owner = false) + : ReadWriteSynchronizedCollectionWrapper(list, owner), IList where TList : class, IList { - [ExcludeFromCodeCoverage] - public ReadWriteSynchronizedListWrapper(TList list, bool owner = false) : base(list, owner) { } - // This is a simplified version. // It could be possible to allow indexed values to change independently of one another. // If that fine grained of read-write control is necessary, then use the ThreadSafety utility and extensions. @@ -58,12 +59,12 @@ public override bool Remove(T item) } } +/// +/// A synchronized wrapper for a list that uses a for synchronization. +/// [ExcludeFromCodeCoverage] -public class ReadWriteSynchronizedListWrapper - : ReadWriteSynchronizedListWrapper> +public class ReadWriteSynchronizedListWrapper( + IList list, bool owner = false) + : ReadWriteSynchronizedListWrapper>(list, owner) { - public ReadWriteSynchronizedListWrapper(IList list, bool owner = false) - : base(list, owner) - { - } } \ No newline at end of file diff --git a/source/Synchronized/TrackedDictionaryWrapper.cs b/source/Synchronized/TrackedDictionaryWrapper.cs index 44efb4d..c3df836 100644 --- a/source/Synchronized/TrackedDictionaryWrapper.cs +++ b/source/Synchronized/TrackedDictionaryWrapper.cs @@ -5,35 +5,52 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized dictionary that can be tracked for changes. +/// public class TrackedDictionaryWrapper : TrackedCollectionWrapper, TDictionary>, IDictionary + where TKey : notnull where TDictionary : class, IDictionary { + /// + /// Construct a new instance with the provide dictionary and optional synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(TDictionary dictionary, ModificationSynchronizer? sync = null) : base(dictionary, sync) { } + /// + /// Construct a new instance with the provide dictionary and a new synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(TDictionary dictionary, out ModificationSynchronizer sync) : base(dictionary, out sync) { } + /// [ExcludeFromCodeCoverage] public ICollection Keys => InternalSource.Keys; + /// [ExcludeFromCodeCoverage] public ICollection Values => InternalSource.Values; /// [ExcludeFromCodeCoverage] - public bool TryGetValue(TKey key, out TValue value) + public bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#else +#endif + out TValue value) { - TValue v = default!; + TValue? v = default; bool result = Sync!.Reading(() => InternalSource.TryGetValue(key, out v)); - value = v; + value = v!; return result; } @@ -45,6 +62,7 @@ public TValue this[TKey key] set => SetValue(key, value); } + /// public bool SetValue(TKey key, TValue value) => Sync!.Modifying( AssertIsAliveDelegate, @@ -71,6 +89,7 @@ public bool ContainsKey(TKey key) => Sync!.Reading( () => AssertIsAlive() && InternalSource.ContainsKey(key)); + /// protected virtual int AddSynchronized(TKey key, TValue value) { Sync!.Modifying( @@ -96,7 +115,7 @@ public void Add(TKey key, TValue value) /// public bool Remove(TKey key) { - TValue value = default!; + TValue? value = default; return Sync!.Modifying( () => AssertIsAlive() && InternalSource.TryGetValue(key, out value), @@ -109,20 +128,24 @@ public bool Remove(TKey key) version => { if (HasChangedListeners) // Avoid creating KVP unnecessarily. - OnChanged(ItemChange.Removed, KeyValuePair.Create(key, value), version); + OnChanged(ItemChange.Removed, KeyValuePair.Create(key, value!), version); }); } } +/// public class TrackedDictionaryWrapper : TrackedDictionaryWrapper> + where TKey : notnull { + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(IDictionary dictionary, ModificationSynchronizer? sync = null) : base(dictionary, sync) { } + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(IDictionary dictionary, out ModificationSynchronizer sync) : base(dictionary, out sync) @@ -130,33 +153,50 @@ public TrackedDictionaryWrapper(IDictionary dictionary, out Modifi } } +/// public class TrackedDictionary : TrackedDictionaryWrapper + where TKey : notnull { + /// + /// Constructs a new instance with the specified capacity and optional synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(int capacity, ModificationSynchronizer? sync = null) : base(new Dictionary(capacity), sync) { } + /// + /// Constructs a new instance with the specified capacity and a new synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(int capacity, out ModificationSynchronizer sync) : base(new Dictionary(capacity), out sync) { } + /// + /// Constructs a new instance with an optional synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(ModificationSynchronizer? sync = null) : base(new Dictionary(), sync) { } + /// + /// Constructs a new instance with a new synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(out ModificationSynchronizer sync) : base(new Dictionary(), out sync) { } + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] public TrackedDictionary() : this(null) { } } diff --git a/source/Synchronized/TrackedIndexedDictionaryWrapper.cs b/source/Synchronized/TrackedIndexedDictionaryWrapper.cs index e5b31d4..59bfa5d 100644 --- a/source/Synchronized/TrackedIndexedDictionaryWrapper.cs +++ b/source/Synchronized/TrackedIndexedDictionaryWrapper.cs @@ -7,6 +7,7 @@ namespace Open.Collections.Synchronized; /// public class TrackedIndexedDictionaryWrapper : TrackedDictionaryWrapper, IIndexedDictionary + where TKey : notnull where TDictionary : class, IIndexedDictionary { /// @@ -135,6 +136,7 @@ protected override int AddSynchronized(TKey key, TValue value) public class TrackedIndexedDictionaryWrapper : TrackedIndexedDictionaryWrapper> + where TKey : notnull { /// [ExcludeFromCodeCoverage] @@ -153,6 +155,7 @@ public TrackedIndexedDictionaryWrapper(IIndexedDictionary dictiona public sealed class TrackedIndexedDictionary : TrackedIndexedDictionaryWrapper + where TKey : notnull { [ExcludeFromCodeCoverage] public TrackedIndexedDictionary(int capacity, ModificationSynchronizer? sync = null) diff --git a/source/Synchronized/TrackedListWrapper.cs b/source/Synchronized/TrackedListWrapper.cs index a9be517..eb84b5f 100644 --- a/source/Synchronized/TrackedListWrapper.cs +++ b/source/Synchronized/TrackedListWrapper.cs @@ -5,13 +5,22 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized list that tracks changes. +/// public class TrackedListWrapper : TrackedCollectionWrapper>, IList { + /// + /// Constructs a new instance with the specified list and optional modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedListWrapper(IList list, ModificationSynchronizer? sync = null) : base(list, sync) { } + /// + /// Constructs a new instance with the specified list and a new modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedListWrapper(IList list, out ModificationSynchronizer sync) : base(list, out sync) { @@ -24,6 +33,7 @@ public T this[int index] set => SetValue(index, value); } + /// public bool SetValue(int index, T value) => Sync!.Modifying( AssertIsAliveDelegate, @@ -115,32 +125,50 @@ public bool Replace(T target, T replacement, bool throwIfNotFound = false) } } +/// +/// A synchronized list that tracks changes. +/// public sealed class TrackedList : TrackedListWrapper { + /// + /// Constructs a new instance with the specified initial capacity and optional modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(int capacity, ModificationSynchronizer? sync = null) : base(new List(capacity), sync) { } + /// + /// Constructs a new instance with the specified initial capacity and a new modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(int capacity, out ModificationSynchronizer sync) : base(new List(capacity), out sync) { } + /// + /// Constructs a new instance using the provided modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(ModificationSynchronizer? sync) - : base(new List(), sync) + : base([], sync) { } + /// + /// Constructs a new instance with a new modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(out ModificationSynchronizer sync) - : base(new List(), out sync) + : base([], out sync) { } + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] public TrackedList() : this(null) { } } \ No newline at end of file diff --git a/testing/Open.Collections.Tests/BasicCollectionTests.cs b/testing/Open.Collections.Tests/BasicCollectionTests.cs index 2c6a99b..a7d32fd 100644 --- a/testing/Open.Collections.Tests/BasicCollectionTests.cs +++ b/testing/Open.Collections.Tests/BasicCollectionTests.cs @@ -7,18 +7,14 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicCollectionTests +public abstract class BasicCollectionTests(TCollection collection) where TCollection : ICollection, new() { - protected BasicCollectionTests(TCollection collection) - => Collection = collection; - protected BasicCollectionTests() : this(new()) { } - protected readonly TCollection Collection; + protected readonly TCollection Collection = collection; - [Fact] - public virtual TCollection AssertWhenDisposed() + protected virtual TCollection AssertWhenDisposedCore() { // Policy: // Ideally an exception should throw whenever access occurs after disposal. @@ -40,6 +36,10 @@ public virtual TCollection AssertWhenDisposed() return c; } + [Fact] + public void AssertWhenDisposed() + => AssertWhenDisposedCore(); + protected static void ThrowsDisposed(Action action) => Assert.Throws(action); diff --git a/testing/Open.Collections.Tests/BasicDictionaryTests.cs b/testing/Open.Collections.Tests/BasicDictionaryTests.cs index 8257f28..ba7c5a8 100644 --- a/testing/Open.Collections.Tests/BasicDictionaryTests.cs +++ b/testing/Open.Collections.Tests/BasicDictionaryTests.cs @@ -5,18 +5,14 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicDictionaryTests +public abstract class BasicDictionaryTests(TDictionary dictionary) where TDictionary : IDictionary, new() { - protected BasicDictionaryTests(TDictionary dictionary) - => Dictionary = dictionary; - protected BasicDictionaryTests() : this(new()) { } - protected readonly TDictionary Dictionary; + protected readonly TDictionary Dictionary = dictionary; - [Fact] - public TDictionary AssertWhenDisposed() + protected TDictionary AssertWhenDisposedCore() { TDictionary d = new(); d.Add(5, 10); @@ -33,6 +29,10 @@ public TDictionary AssertWhenDisposed() return d; } + [Fact] + public void AssertWhenDisposed() + => AssertWhenDisposedCore(); + static void ThrowsDisposed(Action action) => Assert.Throws(action); diff --git a/testing/Open.Collections.Tests/BasicLinkedListTests.cs b/testing/Open.Collections.Tests/BasicLinkedListTests.cs index f7fd98c..92ef919 100644 --- a/testing/Open.Collections.Tests/BasicLinkedListTests.cs +++ b/testing/Open.Collections.Tests/BasicLinkedListTests.cs @@ -3,12 +3,9 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicLinkedListTests : BasicCollectionTests +public abstract class BasicLinkedListTests(TList collection) : BasicCollectionTests(collection) where TList : ILinkedList, new() { - protected BasicLinkedListTests(TList collection) - : base(collection) { } - protected BasicLinkedListTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/BasicListTests.cs b/testing/Open.Collections.Tests/BasicListTests.cs index d80e120..4b762f2 100644 --- a/testing/Open.Collections.Tests/BasicListTests.cs +++ b/testing/Open.Collections.Tests/BasicListTests.cs @@ -4,17 +4,15 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicListTests - : BasicCollectionTests +public abstract class BasicListTests(TList list) + : BasicCollectionTests(list) where TList : IList, new() { - protected BasicListTests(TList list) : base(list) { } - protected BasicListTests() : this(new()) { } - public override TList AssertWhenDisposed() + protected override TList AssertWhenDisposedCore() { - var list = base.AssertWhenDisposed(); + var list = base.AssertWhenDisposedCore(); if (list is not IDisposable) return list; ThrowsDisposed(() => list.IndexOf(5)); return list; diff --git a/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs b/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs index 5ce5ff9..5932b20 100644 --- a/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs @@ -3,7 +3,7 @@ namespace Open.Collections.Tests; public class ConcurrentListTests : BasicListTests> { - public ConcurrentListTests() : base(new()) + public ConcurrentListTests() : base([]) { } } diff --git a/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs index 9400be5..ea5a262 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs @@ -1,12 +1,9 @@ using Open.Collections.Synchronized; namespace Open.Collections.Tests; + public class LockSyncDictionaryTests - : ParallelDictionaryTests> -{ -} + : ParallelDictionaryTests>; public class LockSyncIndexedDictionaryTests - : ParallelDictionaryTests> -{ -} \ No newline at end of file + : ParallelDictionaryTests>; \ No newline at end of file diff --git a/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs index 3251a1f..1324411 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs @@ -1,10 +1,11 @@ using Open.Collections.Synchronized; namespace Open.Collections.Tests; + public class LockSyncLinkedListTests : BasicLinkedListTests> { - public LockSyncLinkedListTests() : base(new()) + public LockSyncLinkedListTests() : base([]) { } } diff --git a/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs index eb60f4b..517525e 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs @@ -2,6 +2,4 @@ namespace Open.Collections.Tests; public class LockSyncListTests - : ParallelListTests> -{ -} + : ParallelListTests>; diff --git a/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs index 42e3bb3..f9be9a9 100644 --- a/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs @@ -3,9 +3,7 @@ namespace Open.Collections.Tests; -public class OrderedDictionaryTests : OrderedDictionaryTests> -{ -} +public class OrderedDictionaryTests : OrderedDictionaryTests>; public class IndexedDictionaryTests : OrderedDictionaryTests> { diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs index 58a1ce3..79f40ff 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs @@ -2,11 +2,7 @@ namespace Open.Collections.Tests; public class ReadWriteSyncDictionaryTests - : ParallelDictionaryTests> -{ -} + : ParallelDictionaryTests>; public class ReadWriteSyncIndexedDictionaryTests - : ParallelDictionaryTests> -{ -} \ No newline at end of file + : ParallelDictionaryTests>; \ No newline at end of file diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs index 26e0482..1daa2a1 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs @@ -4,7 +4,7 @@ namespace Open.Collections.Tests; public class ReadWriteSyncLinkedListTests : BasicLinkedListTests> { - public ReadWriteSyncLinkedListTests() : base(new()) + public ReadWriteSyncLinkedListTests() : base([]) { } } diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs index 010de79..d6b5a90 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs @@ -2,6 +2,4 @@ namespace Open.Collections.Tests; public class ReadWriteSyncListTests - : ParallelListTests> -{ -} + : ParallelListTests>; diff --git a/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs index 2c0f137..857c4fb 100644 --- a/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs @@ -2,11 +2,7 @@ namespace Open.Collections.Tests; public class TrackedDictionaryTests - : ParallelDictionaryTests> -{ -} + : ParallelDictionaryTests>; public class TrackedIndexedDictionaryTests - : ParallelDictionaryTests> -{ -} \ No newline at end of file + : ParallelDictionaryTests>; \ No newline at end of file diff --git a/testing/Open.Collections.Tests/CombinationTests.cs b/testing/Open.Collections.Tests/CombinationTests.cs index 64ee22e..fea5d6c 100644 --- a/testing/Open.Collections.Tests/CombinationTests.cs +++ b/testing/Open.Collections.Tests/CombinationTests.cs @@ -6,19 +6,19 @@ namespace Open.Collections.Tests; public class CombinationTests { - static readonly ImmutableArray Set1 = ImmutableArray.Create(1, 2); - static readonly ImmutableArray Set2 = ImmutableArray.Create('A', 'C', 'E'); - static readonly ImmutableArray Set3 = ImmutableArray.Create(0, 1); + static readonly ImmutableArray Set1 = [1, 2]; + static readonly ImmutableArray Set2 = ['A', 'C', 'E']; + static readonly ImmutableArray Set3 = [0, 1]; [Fact] public void TestCombination1() { - int[][] expected = new int[][] { - new int[] { 1, 1 }, - new int[] { 1, 2 }, - new int[] { 2, 1 }, - new int[] { 2, 2 }, - }; + int[][] expected = [ + [1, 1], + [1, 2], + [2, 1], + [2, 2], + ]; int[][] actual = Set1.Combinations().ToArray(); Assert.Equal(expected, actual); } @@ -26,11 +26,11 @@ public void TestCombination1() [Fact] public void TestCombination1Distinct() { - int[][] expected = new int[][] { - new int[] { 1, 1 }, - new int[] { 1, 2 }, - new int[] { 2, 2 }, - }; + int[][] expected = [ + [1, 1], + [1, 2], + [2, 2], + ]; int[][] actual = Set1.CombinationsDistinct().ToArray(); Assert.Equal(expected, actual); } @@ -38,17 +38,17 @@ public void TestCombination1Distinct() [Fact] public void TestCombination2() { - char[][] expected = new char[][] { - new char[] { 'A', 'A' }, - new char[] { 'A', 'C' }, - new char[] { 'A', 'E' }, - new char[] { 'C', 'A' }, - new char[] { 'C', 'C' }, - new char[] { 'C', 'E' }, - new char[] { 'E', 'A' }, - new char[] { 'E', 'C' }, - new char[] { 'E', 'E' }, - }; + char[][] expected = [ + ['A', 'A'], + ['A', 'C'], + ['A', 'E'], + ['C', 'A'], + ['C', 'C'], + ['C', 'E'], + ['E', 'A'], + ['E', 'C'], + ['E', 'E'], + ]; char[][] actual = Set2.Combinations(2).ToArray(); Assert.Equal(expected, actual); } @@ -56,14 +56,14 @@ public void TestCombination2() [Fact] public void TestCombination2Distinct() { - char[][] expected = new char[][] { - new char[] { 'A', 'A' }, - new char[] { 'A', 'C' }, - new char[] { 'A', 'E' }, - new char[] { 'C', 'C' }, - new char[] { 'C', 'E' }, - new char[] { 'E', 'E' }, - }; + char[][] expected = [ + ['A', 'A'], + ['A', 'C'], + ['A', 'E'], + ['C', 'C'], + ['C', 'E'], + ['E', 'E'], + ]; char[][] actual = Set2.CombinationsDistinct(2).ToArray(); Assert.Equal(expected, actual); } @@ -71,24 +71,24 @@ public void TestCombination2Distinct() [Fact] public void TestCombination3() { - int[][] expected = new int[][] { - new int[] { 0, 0, 0, 0 }, - new int[] { 0, 0, 0, 1 }, - new int[] { 0, 0, 1, 0 }, - new int[] { 0, 0, 1, 1 }, - new int[] { 0, 1, 0, 0 }, - new int[] { 0, 1, 0, 1 }, - new int[] { 0, 1, 1, 0 }, - new int[] { 0, 1, 1, 1 }, - new int[] { 1, 0, 0, 0 }, - new int[] { 1, 0, 0, 1 }, - new int[] { 1, 0, 1, 0 }, - new int[] { 1, 0, 1, 1 }, - new int[] { 1, 1, 0, 0 }, - new int[] { 1, 1, 0, 1 }, - new int[] { 1, 1, 1, 0 }, - new int[] { 1, 1, 1, 1 }, - }; + int[][] expected = [ + [0, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 0, 1, 1], + [0, 1, 0, 0], + [0, 1, 0, 1], + [0, 1, 1, 0], + [0, 1, 1, 1], + [1, 0, 0, 0], + [1, 0, 0, 1], + [1, 0, 1, 0], + [1, 0, 1, 1], + [1, 1, 0, 0], + [1, 1, 0, 1], + [1, 1, 1, 0], + [1, 1, 1, 1], + ]; int[][] actual = Set3.Combinations(4).ToArray(); Assert.Equal(expected, actual); } diff --git a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj index 0b54a3a..7e8c728 100644 --- a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj +++ b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj @@ -1,9 +1,10 @@  - net6.0 + net9.0 false + IDE0305;IDE0301; @@ -11,18 +12,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/testing/Open.Collections.Tests/OrderedDictionaryTests.cs b/testing/Open.Collections.Tests/OrderedDictionaryTests.cs index d5a6915..cfc4bb3 100644 --- a/testing/Open.Collections.Tests/OrderedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/OrderedDictionaryTests.cs @@ -4,12 +4,9 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class OrderedDictionaryTests : BasicDictionaryTests +public abstract class OrderedDictionaryTests(TDictionary dictionary) : BasicDictionaryTests(dictionary) where TDictionary : IDictionary, new() { - protected OrderedDictionaryTests(TDictionary dictionary) - : base(dictionary) { } - protected OrderedDictionaryTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/ParallelDictionaryTests.cs b/testing/Open.Collections.Tests/ParallelDictionaryTests.cs index fb438bb..986ccf3 100644 --- a/testing/Open.Collections.Tests/ParallelDictionaryTests.cs +++ b/testing/Open.Collections.Tests/ParallelDictionaryTests.cs @@ -4,13 +4,10 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class ParallelDictionaryTests - : BasicDictionaryTests +public abstract class ParallelDictionaryTests(TDictionary dictionary) + : BasicDictionaryTests(dictionary) where TDictionary : IDictionary, new() { - protected ParallelDictionaryTests(TDictionary dictionary) - : base(dictionary) { } - protected ParallelDictionaryTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/ParallelListTests.cs b/testing/Open.Collections.Tests/ParallelListTests.cs index db9d8a3..81955af 100644 --- a/testing/Open.Collections.Tests/ParallelListTests.cs +++ b/testing/Open.Collections.Tests/ParallelListTests.cs @@ -4,13 +4,11 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class ParallelListTests - : BasicListTests +public abstract class ParallelListTests( + TList list) + : BasicListTests(list) where TList : IList, new() { - protected ParallelListTests(TList list) - : base(list) { } - protected ParallelListTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/PermutationTests.cs b/testing/Open.Collections.Tests/PermutationTests.cs index a301aea..1b5d684 100644 --- a/testing/Open.Collections.Tests/PermutationTests.cs +++ b/testing/Open.Collections.Tests/PermutationTests.cs @@ -8,16 +8,16 @@ namespace Open.Collections.Tests; public class PermutationTests { - static readonly ImmutableArray Set1 = ImmutableArray.Create(1, 2); + static readonly ImmutableArray Set1 = [1, 2]; static readonly ReadOnlyMemory Set2 = new char[] { 'A', 'B', 'C' }; [Fact] public void TestPermutation1a() { - int[][] expected = new int[][] { - new int[] { 1, 2 }, - new int[] { 2, 1 }, - }; + int[][] expected = [ + [1, 2], + [2, 1], + ]; int[][] actual = Set1.Permutations().ToArray(); actual.Length.Should().Be(2); Assert.Equal(expected, actual); @@ -33,10 +33,10 @@ public void TestPermutation1a() [InlineData(24)] public void TestPermutation1b(int bufferLength) { - int[][] expected = new int[][] { - new int[] { 1, 2 }, - new int[] { 2, 1 }, - }; + int[][] expected = [ + [1, 2], + [2, 1], + ]; int[] buffer = new int[bufferLength]; @@ -48,14 +48,14 @@ public void TestPermutation1b(int bufferLength) [Fact] public void TestPermutation2() { - char[][] expected = new char[][] { - new char[] { 'A', 'B', 'C' }, - new char[] { 'B', 'A', 'C' }, - new char[] { 'C', 'A', 'B' }, - new char[] { 'A', 'C', 'B' }, - new char[] { 'B', 'C', 'A' }, - new char[] { 'C', 'B', 'A' }, - }; + char[][] expected = [ + ['A', 'B', 'C'], + ['B', 'A', 'C'], + ['C', 'A', 'B'], + ['A', 'C', 'B'], + ['B', 'C', 'A'], + ['C', 'B', 'A'], + ]; char[][] actual = Set2.Permutations().ToArray(); Assert.Equal(expected, actual); } diff --git a/testing/Open.Collections.Tests/PermutorTests.cs b/testing/Open.Collections.Tests/PermutorTests.cs index f9f3a16..a183465 100644 --- a/testing/Open.Collections.Tests/PermutorTests.cs +++ b/testing/Open.Collections.Tests/PermutorTests.cs @@ -12,7 +12,7 @@ public class PermutorTests [Fact] public void TestNoDuplicatePermutations() { - int[] numbers = { 1, 2, 3, 4 }; + int[] numbers = [1, 2, 3, 4]; var permutations = numbers.Permutations().Select(p => string.Join(",", p.Span.ToArray())).ToHashSet(); int expectedCount = Factorial(numbers.Length); @@ -22,17 +22,17 @@ public void TestNoDuplicatePermutations() [Fact] public void TestSpecificPermutation() { - int[] numbers = { 1, 2, 3 }; + int[] numbers = [1, 2, 3]; var permutations = numbers.AsMemory().Permutations().Select(m=>m.ToArray()).ToList(); permutations.Count.Should().Be(6); - int[] expectedPermutation = new[] { 2, 1, 3 }; + int[] expectedPermutation = [2, 1, 3]; Assert.Contains(expectedPermutation, permutations); } [Fact] public void TestExactPermutationsFor123() { - int[] numbers = { 1, 2, 3 }; + int[] numbers = [1, 2, 3]; var expectedPermutations = new List { "1,2,3", @@ -57,7 +57,7 @@ public void TestExactPermutationsFor123() [Fact] public void TestStableLexicographicOrder() { - int[] original = { 1, 2, 3 }; + int[] original = [1, 2, 3]; Span span = original.AsSpan(); var permutations = new List(); @@ -80,11 +80,10 @@ public void TestStableLexicographicOrder() Assert.Equal(expectedPermutations, permutations); } - [Fact] public void TestStableHeapsAlgorithmOrder() { - int[] original = { 1, 2, 3 }; + int[] original = [1, 2, 3]; var permutations = new List(); foreach(var s in original.Permutations()) permutations.Add(string.Join(",", s.ToArray())); @@ -105,7 +104,7 @@ public void TestStableHeapsAlgorithmOrder() [Fact] public void TestStableIndexedOrder() { - int[] original = { 1, 2, 3 }; + int[] original = [1, 2, 3]; var permutations = new List(); for (int i = 0; i < 6; ++i) { @@ -129,7 +128,6 @@ public void TestStableIndexedOrder() Assert.Equal(expectedPermutations, permutations); } - private static int Factorial(int n) { int result = 1; diff --git a/testing/Open.Collections.Tests/SubsetTests.cs b/testing/Open.Collections.Tests/SubsetTests.cs index 2681c17..a276346 100644 --- a/testing/Open.Collections.Tests/SubsetTests.cs +++ b/testing/Open.Collections.Tests/SubsetTests.cs @@ -8,20 +8,20 @@ namespace Open.Collections.Tests; public class SubsetTests { - static readonly ImmutableArray Set1 = ImmutableArray.Create(1, 2, 3); - static readonly ImmutableArray Set2 = ImmutableArray.Create('A', 'C', 'E'); - static readonly ImmutableArray Set3 = ImmutableArray.Create('A', 'B', 'C', 'D'); + static readonly ImmutableArray Set1 = [1, 2, 3]; + static readonly ImmutableArray Set2 = ['A', 'C', 'E']; + static readonly ImmutableArray Set3 = ['A', 'B', 'C', 'D']; static readonly ImmutableArray Set4 = Enumerable.Range(1, 5).ToImmutableArray(); static readonly ImmutableArray Set5 = Enumerable.Range(1, 11).ToImmutableArray(); [Fact] public void TestSubset1_2() { - int[][] expected = new int[][] { - new int[] { 1, 2 }, - new int[] { 1, 3 }, - new int[] { 2, 3 }, - }; + int[][] expected = [ + [1, 2], + [1, 3], + [2, 3], + ]; int[][] actual = Set1.Subsets(2).ToArray(); Assert.Equal(expected, actual); @@ -35,11 +35,11 @@ public void TestSubset1_2() [Fact] public void TestSubset2_2() { - char[][] expected = new char[][] { - new char[] { 'A', 'C' }, - new char[] { 'A', 'E' }, - new char[] { 'C', 'E' }, - }; + char[][] expected = [ + ['A', 'C'], + ['A', 'E'], + ['C', 'E'], + ]; char[][] actual = Set2.Subsets(2).ToArray(); Assert.Equal(expected, actual); @@ -50,14 +50,14 @@ public void TestSubset2_2() [Fact] public void TestSubset3_2() { - char[][] expected = new char[][] { - new char[] { 'A', 'B' }, - new char[] { 'A', 'C' }, - new char[] { 'B', 'C' }, - new char[] { 'A', 'D' }, - new char[] { 'B', 'D' }, - new char[] { 'C', 'D' }, - }; + char[][] expected = [ + ['A', 'B'], + ['A', 'C'], + ['B', 'C'], + ['A', 'D'], + ['B', 'D'], + ['C', 'D'], + ]; Assert.Equal(expected.Length, Set3.Subsets(2).Count()); char[][] progressive = Set3.SubsetsProgressive(2).ToArray(); @@ -67,12 +67,12 @@ public void TestSubset3_2() [Fact] public void TestSubset3_3() { - char[][] expected = new char[][] { - new char[] { 'A', 'B', 'C' }, - new char[] { 'A', 'B', 'D' }, - new char[] { 'A', 'C', 'D' }, - new char[] { 'B', 'C', 'D' }, - }; + char[][] expected = [ + ['A', 'B', 'C'], + ['A', 'B', 'D'], + ['A', 'C', 'D'], + ['B', 'C', 'D'], + ]; char[][] actual = Set3.Subsets(3).ToArray(); Assert.Equal(expected, actual); @@ -100,13 +100,13 @@ static T[] Selector(ArrayPoolSegment e) [Fact] public void TestSubset4_4() { - int[][] expected = new int[][] { - new int[] { 1, 2, 3, 4 }, - new int[] { 1, 2, 3, 5 }, - new int[] { 1, 2, 4, 5 }, - new int[] { 1, 3, 4, 5 }, - new int[] { 2, 3, 4, 5 }, - }; + int[][] expected = [ + [1, 2, 3, 4], + [1, 2, 3, 5], + [1, 2, 4, 5], + [1, 3, 4, 5], + [2, 3, 4, 5], + ]; int[][] actual = Set4.Subsets(4).ToArray(); Assert.Equal(expected, actual); @@ -127,18 +127,18 @@ public void TestSubset4_4() [Fact] public void TestSubset4_3() { - int[][] expected = new int[][] { - new int[] { 1, 2, 3 }, - new int[] { 1, 2, 4 }, - new int[] { 1, 3, 4 }, - new int[] { 2, 3, 4 }, - new int[] { 1, 2, 5 }, - new int[] { 1, 3, 5 }, - new int[] { 1, 4, 5 }, - new int[] { 2, 3, 5 }, - new int[] { 2, 4, 5 }, - new int[] { 3, 4, 5 }, - }; + int[][] expected = [ + [1, 2, 3], + [1, 2, 4], + [1, 3, 4], + [2, 3, 4], + [1, 2, 5], + [1, 3, 5], + [1, 4, 5], + [2, 3, 5], + [2, 4, 5], + [3, 4, 5], + ]; int[][] actual = Set4.SubsetsProgressive(3).ToArray(); Assert.Equal(expected, actual); diff --git a/testing/Open.Collections.Tests/TrieTests.cs b/testing/Open.Collections.Tests/TrieTests.cs index fd9f5fd..ad2da83 100644 --- a/testing/Open.Collections.Tests/TrieTests.cs +++ b/testing/Open.Collections.Tests/TrieTests.cs @@ -4,15 +4,15 @@ namespace Open.Collections.Tests; public static class TrieTests { - static readonly string[] Examples = new[] - { + static readonly string[] Examples = + [ "", "abcd", "dcba", "abcdef", "the brown fox", "xxx" - }; + ]; [Fact] public static void TrieValidate() From 7aa3d055982c8a2bf180f49e2eb88cbb78c24480 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:32:03 -0800 Subject: [PATCH 04/18] Refactor to use primary constructors and update targets Refactored multiple classes to use C# 9.0 primary constructors, simplifying initialization and improving readability. Updated project files to target .NET 9.0 and included new package references. Suppressed specific style warnings and added XML documentation to various classes. Enhanced thread-safety and nullability handling in several methods. Improved test classes with modern C# features and updated array initializations. --- .editorconfig | 10 +- Trie/ConcurrentTrie.cs | 19 +- Trie/Open.Collections.Trie.csproj | 5 +- Trie/StringJoinPool.cs | 20 +-- .../MaybeNullWhenAttribute.cs | 14 +- Trie/Trie.cs | 23 +-- Trie/TrieBase.cs | 27 +-- .../Benchmarks/CollectionBenchmark.cs | 8 +- .../Benchmarks/CollectionParallelBenchmark.cs | 17 +- .../Benchmarks/DictionaryParallelBenchmark.cs | 9 +- .../Benchmarks/LinkedListBenchmark.cs | 8 +- .../Benchmarks/LinkedListParallelBenchmark.cs | 8 +- .../Benchmarks/ListParallelBenchmark.cs | 9 +- benchmarking/Benchmarks/QueueBenchmark.cs | 7 +- .../Benchmarks/QueueParallelBenchmark.cs | 6 +- .../Benchmarks/SubsetBufferedBench.cs | 4 +- benchmarking/Benchmarks/TrieBenchmarks.cs | 2 +- .../Open.Collections.Benchmarking.csproj | 5 +- benchmarking/Program.cs | 3 + source/ArrayPoolSegment.cs | 5 +- source/CollectionWrapper.cs | 31 +++- source/ConcurrentHashSet.cs | 7 + source/DictionaryToHashSetWrapper.cs | 11 +- source/DictionaryWrapper.cs | 8 +- source/DictionaryWrapperBase.cs | 37 +++- source/Extensions.Combinations.cs | 4 +- source/Extensions.ConcurrentDictionary.cs | 10 +- source/Extensions.Generic.Synchronized.cs | 45 +++-- source/Extensions.Generic.cs | 16 +- source/Extensions.Stream.cs | 12 +- source/Extensions.cs | 165 ++++++++++-------- source/IndexedDictionary.cs | 21 ++- source/ItemChangedEventArgs.cs | 25 ++- source/LazyList.cs | 35 ++-- source/LazyListUnsafe.cs | 51 +++--- source/LinkedList/ILinkedList.cs | 7 +- source/ListWrapper.cs | 12 +- source/Open.Collections.csproj | 18 +- source/OrderedDictionary.cs | 27 ++- source/Queue/IQueue.cs | 18 +- source/Queue/Queue.Standard.cs | 22 ++- source/ReadOnlyCollectionAdapter.cs | 18 +- source/ReadOnlyCollectionWrapper.cs | 25 +++ source/Synchronized/ConcurrentList.cs | 15 +- .../LockSynchronizedCollectionWrapper.cs | 14 +- .../LockSynchronizedDictionaryWrapper.cs | 23 ++- .../Synchronized/LockSynchronizedHashSet.cs | 14 +- .../LockSynchronizedIndexedDictionary.cs | 10 +- .../LockSynchronizedLinkedList.cs | 4 +- source/Synchronized/LockSynchronizedList.cs | 16 +- .../LockSynchronizedListWrapper.cs | 15 +- .../LockSynchronizedOrderedDictionary.cs | 10 +- source/Synchronized/LockSynchronizedQueue.cs | 14 +- .../ReadWriteSynchronizedDictionaryWrapper.cs | 24 +-- .../ReadWriteSynchronizedHashSet.cs | 15 +- .../ReadWriteSynchronizedLinkedList.cs | 15 +- .../Synchronized/ReadWriteSynchronizedList.cs | 14 +- .../ReadWriteSynchronizedListWrapper.cs | 23 +-- .../Synchronized/TrackedDictionaryWrapper.cs | 50 +++++- .../TrackedIndexedDictionaryWrapper.cs | 3 + source/Synchronized/TrackedListWrapper.cs | 32 +++- .../BasicCollectionTests.cs | 14 +- .../BasicDictionaryTests.cs | 14 +- .../BasicLinkedListTests.cs | 5 +- .../Open.Collections.Tests/BasicListTests.cs | 10 +- .../Collections/ConcurrentListTests.cs | 3 +- .../Collections/LockSyncDictionaryTests.cs | 9 +- .../Collections/LockSyncLinkedListTests.cs | 3 +- .../Collections/LockSyncListTests.cs | 4 +- .../Collections/OrderedDictionaryTests.cs | 4 +- .../ReadWriteSyncLDictionaryTests.cs | 8 +- .../ReadWriteSyncLinkedListTests.cs | 2 +- .../Collections/ReadWriteSyncListTests.cs | 4 +- .../Collections/TrackedDictionaryTests.cs | 8 +- .../CombinationTests.cs | 102 +++++------ .../Open.Collections.Tests.csproj | 15 +- .../OrderedDictionaryTests.cs | 5 +- .../ParallelDictionaryTests.cs | 7 +- .../ParallelListTests.cs | 8 +- .../PermutationTests.cs | 34 ++-- .../Open.Collections.Tests/PermutorTests.cs | 16 +- testing/Open.Collections.Tests/SubsetTests.cs | 92 +++++----- testing/Open.Collections.Tests/TrieTests.cs | 6 +- 83 files changed, 892 insertions(+), 626 deletions(-) diff --git a/.editorconfig b/.editorconfig index 76e37ea..4ab8963 100644 --- a/.editorconfig +++ b/.editorconfig @@ -226,6 +226,8 @@ dotnet_diagnostic.IDE0057.severity = silent dotnet_diagnostic.CS1591.severity = suggestion csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion #dotnet_diagnostic.SA0001.severity = suggestion [*.{cs,vb}] dotnet_style_coalesce_expression = true:warning @@ -234,4 +236,10 @@ tab_width = 4 indent_size = 4 end_of_line = crlf dotnet_style_null_propagation = true:warning -indent_style = tab \ No newline at end of file +indent_style = tab +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_diagnostic.CA2007.severity = error \ No newline at end of file diff --git a/Trie/ConcurrentTrie.cs b/Trie/ConcurrentTrie.cs index c360d65..2f7d096 100644 --- a/Trie/ConcurrentTrie.cs +++ b/Trie/ConcurrentTrie.cs @@ -6,22 +6,13 @@ namespace Open.Collections; /// /// A generic Trie collection. /// -public sealed class ConcurrentTrie - : TrieBase +public sealed class ConcurrentTrie( + IEqualityComparer? equalityComparer = null) + : TrieBase(() => new Node(equalityComparer)) where TKey : notnull { - /// - /// Constructs a . - /// - public ConcurrentTrie(IEqualityComparer? equalityComparer = null) - : base(() => new Node(equalityComparer)) - { } - - private sealed class Node : NodeBase + private sealed class Node(IEqualityComparer? equalityComparer) : NodeBase { - public Node(IEqualityComparer? equalityComparer) - => _equalityComparer = equalityComparer; - private readonly object _valueSync = new(); protected override void SetValue(TValue value) @@ -31,7 +22,7 @@ protected override void SetValue(TValue value) private readonly object _childSync = new(); - private readonly IEqualityComparer? _equalityComparer; + private readonly IEqualityComparer? _equalityComparer = equalityComparer; private ConcurrentDictionary>? _children; protected override void UpdateRecent(TKey key, ITrieNode child) diff --git a/Trie/Open.Collections.Trie.csproj b/Trie/Open.Collections.Trie.csproj index ca0fdee..aa1f8a0 100644 --- a/Trie/Open.Collections.Trie.csproj +++ b/Trie/Open.Collections.Trie.csproj @@ -26,6 +26,7 @@ snupkg logo.png README.md + IDE0130; @@ -44,11 +45,11 @@ - + - + \ No newline at end of file diff --git a/Trie/StringJoinPool.cs b/Trie/StringJoinPool.cs index 1280e0a..1905883 100644 --- a/Trie/StringJoinPool.cs +++ b/Trie/StringJoinPool.cs @@ -12,22 +12,12 @@ namespace Open.Collections; /// /// Useful for (re)generating cache keys. /// -public class StringJoinPool +public class StringJoinPool( + ITrie pool, ReadOnlyMemory separator) { - private readonly ReadOnlyMemory _separator; - private readonly ITrie _pool; + private readonly ITrie _pool = pool ?? throw new ArgumentNullException(nameof(pool)); private StringBuilder? _reusableBuilder; - /// - /// Constructs a . - /// - /// If the supplied pool is null. - public StringJoinPool(ITrie pool, ReadOnlyMemory separator) - { - _pool = pool ?? throw new ArgumentNullException(nameof(pool)); - _separator = separator; - } - /// public StringJoinPool(ITrie pool, string? separator = null) : this(pool, separator is null ? ReadOnlyMemory.Empty : separator.AsMemory()) @@ -75,7 +65,7 @@ string Build(ReadOnlySpan segments) int len = segments.Length; try { - if (_separator.IsEmpty) + if (separator.IsEmpty) { for (int i = 0; i < len; i++) AppendSegment(sb, segments[i]); @@ -85,7 +75,7 @@ string Build(ReadOnlySpan segments) Debug.Assert(segments.Length != 0); AppendSegment(sb, segments[0]); - var sepSpan = _separator.Span; + var sepSpan = separator.Span; int sLen = sepSpan.Length; for (int i = 1; i < len; i++) diff --git a/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs b/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs index a8f0c0c..ce5e8ea 100644 --- a/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs +++ b/Trie/System.Diagnostics.CodeAnalysis/MaybeNullWhenAttribute.cs @@ -1,4 +1,5 @@ #if NETSTANDARD2_0 + namespace System.Diagnostics.CodeAnalysis; // Use a shim for simplicity. @@ -6,18 +7,15 @@ namespace System.Diagnostics.CodeAnalysis; /// /// Indicates that the output may be null even if the corresponding type disallows it. /// +/// +/// Constructs a . +/// [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class MaybeNullWhenAttribute : Attribute +internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute { - /// - /// Constructs a . - /// - public MaybeNullWhenAttribute(bool returnValue) - => ReturnValue = returnValue; - /// /// The return value condition. /// - public bool ReturnValue { get; } + public bool ReturnValue { get; } = returnValue; } #endif \ No newline at end of file diff --git a/Trie/Trie.cs b/Trie/Trie.cs index 9ac5a8b..393c47d 100644 --- a/Trie/Trie.cs +++ b/Trie/Trie.cs @@ -4,34 +4,25 @@ namespace Open.Collections; /// /// A generic Trie collection. /// -public sealed class Trie - : TrieBase +public sealed class Trie( + IEqualityComparer? equalityComparer = null) + : TrieBase(() => new Node(equalityComparer)) where TKey : notnull { - /// - /// Constructs a . - /// - public Trie(IEqualityComparer? equalityComparer = null) - : base(() => new Node(equalityComparer)) - { } - - private sealed class Node : NodeBase + private sealed class Node(IEqualityComparer? equalityComparer) + : NodeBase { - private readonly IEqualityComparer? _equalityComparer; private Dictionary>? _children; - public Node(IEqualityComparer? equalityComparer) - => _equalityComparer = equalityComparer; - public override ITrieNode GetOrAddChild(TKey key) { var children = _children; if (children is null) - Children = _children = children = _equalityComparer is null ? new() : new(_equalityComparer); + Children = _children = children = equalityComparer is null ? new() : new(equalityComparer); else if (TryGetChildFrom(children, key, out var c)) return c; - var child = new Node(_equalityComparer); + var child = new Node(equalityComparer); children[key] = child; return child; } diff --git a/Trie/TrieBase.cs b/Trie/TrieBase.cs index 77c5dd2..0e65568 100644 --- a/Trie/TrieBase.cs +++ b/Trie/TrieBase.cs @@ -221,34 +221,23 @@ internal abstract class NodeBase : ITrieNode { protected IDictionary>? Children; - private readonly struct ValueContainer + private readonly struct ValueContainer(bool isSet, TValue value) { - public ValueContainer(bool isSet, TValue value) - { - IsSet = isSet; - Value = value; - } - public ValueContainer(TValue value) : this(true, value) { } - public bool IsSet { get; } - public TValue Value { get; } + public bool IsSet { get; } = isSet; + public TValue Value { get; } = value; } private ValueContainer _value; - private readonly struct Recent + private readonly struct Recent( + bool exists, TKey key, ITrieNode child) { - public Recent(bool exists, TKey key, ITrieNode child) - { - Exists = exists; - Key = key; - Child = child; - } - public bool Exists { get; } - public TKey Key { get; } - public ITrieNode Child { get; } + public bool Exists { get; } = exists; + public TKey Key { get; } = key; + public ITrieNode Child { get; } = child; } // It's not uncommon to have a 'hot path' that will be requested frequently. diff --git a/benchmarking/Benchmarks/CollectionBenchmark.cs b/benchmarking/Benchmarks/CollectionBenchmark.cs index 184f2d2..3d002bf 100644 --- a/benchmarking/Benchmarks/CollectionBenchmark.cs +++ b/benchmarking/Benchmarks/CollectionBenchmark.cs @@ -72,13 +72,9 @@ protected override IEnumerable TestOnceInternal() } } -public class CollectionBenchmark : CollectionBenchmark +public class CollectionBenchmark( + uint size, uint repeat, Func> factory) : CollectionBenchmark(size, repeat, factory, _ => new object()) { - public CollectionBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory, _ => new object()) - { - } - public static TimedResult[] Results(uint size, uint repeat, Func> factory, Func itemFactory) => new CollectionBenchmark(size, repeat, factory, itemFactory).Result; diff --git a/benchmarking/Benchmarks/CollectionParallelBenchmark.cs b/benchmarking/Benchmarks/CollectionParallelBenchmark.cs index 67209b3..721e1c9 100644 --- a/benchmarking/Benchmarks/CollectionParallelBenchmark.cs +++ b/benchmarking/Benchmarks/CollectionParallelBenchmark.cs @@ -6,12 +6,10 @@ namespace Open.Collections; -public class CollectionParallelBenchmark : CollectionBenchmark +public class CollectionParallelBenchmark( + uint size, uint repeat, Func> factory, Func itemFactory) + : CollectionBenchmark(size, repeat, factory, itemFactory) { - public CollectionParallelBenchmark(uint size, uint repeat, Func> factory, Func itemFactory) : base(size, repeat, factory, itemFactory) - { - } - protected override IEnumerable TestOnceInternal() { ICollection c = Param(); @@ -115,13 +113,10 @@ protected override IEnumerable TestOnceInternal() } } -public class CollectionParallelBenchmark : CollectionParallelBenchmark +public class CollectionParallelBenchmark( + uint size, uint repeat, Func> factory) + : CollectionParallelBenchmark(size, repeat, factory, _ => new object()) { - public CollectionParallelBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory, _ => new object()) - { - } - public static TimedResult[] Results(uint size, uint repeat, Func> factory, Func itemFactory) => new CollectionParallelBenchmark(size, repeat, factory, itemFactory).Result; diff --git a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs index 9b94aff..f45e2f4 100644 --- a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs +++ b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs @@ -7,13 +7,10 @@ namespace Open.Collections; -public class DictionaryParallelBenchmark : CollectionParallelBenchmark> +public class DictionaryParallelBenchmark( + uint size, uint repeat, Func> factory) + : CollectionParallelBenchmark>(size, repeat, factory, i => new KeyValuePair(i, new object())) { - public DictionaryParallelBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory, i => new KeyValuePair(i, new object())) - { - } - protected override IEnumerable TestOnceInternal() { //foreach (TimedResult t in base.TestOnceInternal()) diff --git a/benchmarking/Benchmarks/LinkedListBenchmark.cs b/benchmarking/Benchmarks/LinkedListBenchmark.cs index fc5a0a8..c5c8655 100644 --- a/benchmarking/Benchmarks/LinkedListBenchmark.cs +++ b/benchmarking/Benchmarks/LinkedListBenchmark.cs @@ -4,12 +4,10 @@ namespace Open.Collections; -public class LinkedListBenchmark : BenchmarkBase>> +public class LinkedListBenchmark( + uint size, uint repeat, Func> factory) + : BenchmarkBase>>(size, repeat, factory) { - public LinkedListBenchmark(uint size, uint repeat, Func> factory) : base(size, repeat, factory) - { - } - protected readonly object _item = new(); protected override IEnumerable TestOnceInternal() diff --git a/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs b/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs index c29c025..ae5990b 100644 --- a/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs +++ b/benchmarking/Benchmarks/LinkedListParallelBenchmark.cs @@ -5,12 +5,10 @@ namespace Open.Collections; -public class LinkedListParallelBenchmark : LinkedListBenchmark +public class LinkedListParallelBenchmark( + uint size, uint repeat, Func> factory) + : LinkedListBenchmark(size, repeat, factory) { - public LinkedListParallelBenchmark(uint size, uint repeat, Func> factory) : base(size, repeat, factory) - { - } - protected override IEnumerable TestOnceInternal() { ILinkedList c = Param(); diff --git a/benchmarking/Benchmarks/ListParallelBenchmark.cs b/benchmarking/Benchmarks/ListParallelBenchmark.cs index 9a8e061..3f17069 100644 --- a/benchmarking/Benchmarks/ListParallelBenchmark.cs +++ b/benchmarking/Benchmarks/ListParallelBenchmark.cs @@ -4,13 +4,10 @@ namespace Open.Collections; -public class ListParallelBenchmark : CollectionParallelBenchmark +public class ListParallelBenchmark( + uint size, uint repeat, Func> factory) + : CollectionParallelBenchmark(size, repeat, factory) { - public ListParallelBenchmark(uint size, uint repeat, Func> factory) - : base(size, repeat, factory) - { - } - // Get/Set (mutating entry) operations have no benefit to synchronization and are inherently thread safe. //protected override IEnumerable TestOnceInternal() //{ diff --git a/benchmarking/Benchmarks/QueueBenchmark.cs b/benchmarking/Benchmarks/QueueBenchmark.cs index 0839c64..42a7f10 100644 --- a/benchmarking/Benchmarks/QueueBenchmark.cs +++ b/benchmarking/Benchmarks/QueueBenchmark.cs @@ -4,13 +4,8 @@ namespace Open.Collections; -public class QueueBenchmark : BenchmarkBase>> +public class QueueBenchmark(uint size, uint repeat, Func> queueFactory) : BenchmarkBase>>(size, repeat, queueFactory) { - public QueueBenchmark(uint size, uint repeat, Func> queueFactory) - : base(size, repeat, queueFactory) - { - } - protected readonly object _item = new(); protected override IEnumerable TestOnceInternal() diff --git a/benchmarking/Benchmarks/QueueParallelBenchmark.cs b/benchmarking/Benchmarks/QueueParallelBenchmark.cs index f9d23d3..dde600d 100644 --- a/benchmarking/Benchmarks/QueueParallelBenchmark.cs +++ b/benchmarking/Benchmarks/QueueParallelBenchmark.cs @@ -5,12 +5,8 @@ namespace Open.Collections; -public class QueueParallelBenchmark : QueueBenchmark +public class QueueParallelBenchmark(uint size, uint repeat, Func> factory) : QueueBenchmark(size, repeat, factory) { - public QueueParallelBenchmark(uint size, uint repeat, Func> factory) : base(size, repeat, factory) - { - } - protected override IEnumerable TestOnceInternal() { IQueue queue = Param(); diff --git a/benchmarking/Benchmarks/SubsetBufferedBench.cs b/benchmarking/Benchmarks/SubsetBufferedBench.cs index 81553f0..9ec3efb 100644 --- a/benchmarking/Benchmarks/SubsetBufferedBench.cs +++ b/benchmarking/Benchmarks/SubsetBufferedBench.cs @@ -22,7 +22,7 @@ namespace Open.Collections.Benchmarks; //[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")] public class SubsetBufferedBench { - IReadOnlyList FullSet = Array.Empty(); + int[] FullSet = Array.Empty(); ReadOnlyMemory FullMemorySet = ReadOnlyMemory.Empty; [Params(3, 7)] @@ -31,7 +31,7 @@ public class SubsetBufferedBench [Params(9, 32)] public int Range { - get => FullSet.Count; + get => FullSet.Length; set { int[] s = Enumerable.Range(0, value).ToArray(); diff --git a/benchmarking/Benchmarks/TrieBenchmarks.cs b/benchmarking/Benchmarks/TrieBenchmarks.cs index 881cfe1..4bfde35 100644 --- a/benchmarking/Benchmarks/TrieBenchmarks.cs +++ b/benchmarking/Benchmarks/TrieBenchmarks.cs @@ -35,7 +35,7 @@ public void GlobalSetup() { int x = 0; trie = new(); - dictionary = new(); + dictionary = []; ctrie = new(); cdictionary = new(); keys = GenerateTree(Depth, NodeSize) diff --git a/benchmarking/Open.Collections.Benchmarking.csproj b/benchmarking/Open.Collections.Benchmarking.csproj index cd1aa13..4e951ed 100644 --- a/benchmarking/Open.Collections.Benchmarking.csproj +++ b/benchmarking/Open.Collections.Benchmarking.csproj @@ -2,9 +2,10 @@ Exe - net7.0 + net9.0 Open.Collections latest + IDE0305;IDE0301;IDE0130; @@ -14,7 +15,7 @@ - + diff --git a/benchmarking/Program.cs b/benchmarking/Program.cs index 3e0d9c3..9e20caa 100644 --- a/benchmarking/Program.cs +++ b/benchmarking/Program.cs @@ -13,6 +13,7 @@ namespace Open.Collections.Benchmarks; [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1213:Remove unused member declaration.", Justification = "")] internal static class Program { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0022:Use expression body for method")] static void Main() { //BenchmarkRunner.Run(); @@ -141,6 +142,7 @@ static void LinkedListTests() // report.Test(1000); //} + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0028:Simplify collection initialization")] static void ListTests() { Console.WriteLine("::: Synchronized Lists :::\n"); @@ -164,6 +166,7 @@ static void ListTests() report.Test(4000, 4); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0028:Simplify collection initialization")] static void HashSetTests() { Console.WriteLine("::: Synchronized HashSets :::\n"); diff --git a/source/ArrayPoolSegment.cs b/source/ArrayPoolSegment.cs index 3c28cc4..74940e0 100644 --- a/source/ArrayPoolSegment.cs +++ b/source/ArrayPoolSegment.cs @@ -14,6 +14,9 @@ namespace Open.Collections; public readonly ArrayPool? Pool; private readonly bool _clear; + /// + /// Constructs a new from the . + /// public ArrayPoolSegment( int length, ArrayPool? pool = null, @@ -29,7 +32,7 @@ public ArrayPoolSegment( /// Returns the array to the pool. /// /// - public void Dispose() => Pool?.Return(Segment.Array, _clear); + public void Dispose() => Pool?.Return(Segment.Array!, _clear); /// /// Implicitly converts the to an . diff --git a/source/CollectionWrapper.cs b/source/CollectionWrapper.cs index ce35ff2..de8a90a 100644 --- a/source/CollectionWrapper.cs +++ b/source/CollectionWrapper.cs @@ -4,26 +4,39 @@ namespace Open.Collections; +/// +/// A disposable wrapper for a collection. +/// [ExcludeFromCodeCoverage] -public class CollectionWrapper - : ReadOnlyCollectionWrapper, ICollection, IAddMultiple +public class CollectionWrapper( + TCollection source, bool owner = false) + : ReadOnlyCollectionWrapper(source, owner), ICollection, IAddMultiple where TCollection : class, ICollection { - public CollectionWrapper(TCollection source, bool owner = false) - : base(source, owner) - { - } - - protected readonly object Sync = new(); // Could possibly override.. - /// /// The underlying object used for synchronization. + /// +#if NET9_0_OR_GREATER + protected readonly System.Threading.Lock Sync = new(); +#else + protected readonly object Sync = new(); +#endif + + /// + /// The object used for synchronization. /// This is exposed to allow for more complex synchronization operations. /// +#if NET9_0_OR_GREATER + public System.Threading.Lock SyncRoot => Sync; +#else public object SyncRoot => Sync; +#endif #region Implementation of ICollection + /// + /// Manages adding an item to the collection. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected virtual void AddInternal(in T item) => InternalUnsafeSource!.Add(item); diff --git a/source/ConcurrentHashSet.cs b/source/ConcurrentHashSet.cs index e55ab57..68cf492 100644 --- a/source/ConcurrentHashSet.cs +++ b/source/ConcurrentHashSet.cs @@ -4,9 +4,16 @@ namespace Open.Collections; +/// +/// A thread-safe hash by wrapping a . +/// [ExcludeFromCodeCoverage] public sealed class ConcurrentHashSet : DictionaryToHashSetWrapper + where T : notnull { + /// + /// Construct a new instance with optoinal initial values. + /// public ConcurrentHashSet(IEnumerable? intialValues = null) : base(new ConcurrentDictionary()) { diff --git a/source/DictionaryToHashSetWrapper.cs b/source/DictionaryToHashSetWrapper.cs index 6f7a81a..5c43dbf 100644 --- a/source/DictionaryToHashSetWrapper.cs +++ b/source/DictionaryToHashSetWrapper.cs @@ -5,13 +5,12 @@ namespace Open.Collections; -public class DictionaryToHashSetWrapper : ISet +[method: ExcludeFromCodeCoverage] +public class DictionaryToHashSetWrapper( + IDictionary source) + : ISet { - protected readonly IDictionary InternalSource; - - [ExcludeFromCodeCoverage] - public DictionaryToHashSetWrapper(IDictionary source) - => InternalSource = source; + protected readonly IDictionary InternalSource = source; /// [ExcludeFromCodeCoverage] diff --git a/source/DictionaryWrapper.cs b/source/DictionaryWrapper.cs index 0e9a6b8..086fc93 100644 --- a/source/DictionaryWrapper.cs +++ b/source/DictionaryWrapper.cs @@ -8,6 +8,7 @@ namespace Open.Collections; [ExcludeFromCodeCoverage] public class DictionaryWrapper : DictionaryWrapperBase> + where TKey : notnull { /// public DictionaryWrapper() @@ -61,6 +62,11 @@ public override bool Remove(TKey key) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool TryGetValue(TKey key, out TValue value) + public override bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#else +#endif + out TValue value) => InternalSource.TryGetValue(key, out value); } diff --git a/source/DictionaryWrapperBase.cs b/source/DictionaryWrapperBase.cs index a6e8273..3bc81b6 100644 --- a/source/DictionaryWrapperBase.cs +++ b/source/DictionaryWrapperBase.cs @@ -4,16 +4,16 @@ namespace Open.Collections; +/// +/// A base class for wrapping a collection as a dictionary. +/// [ExcludeFromCodeCoverage] -public abstract class DictionaryWrapperBase - : CollectionWrapper, TCollection>, IDictionary +public abstract class DictionaryWrapperBase( + TCollection source, bool owner = false) + : CollectionWrapper, TCollection>(source, owner), IDictionary + where TKey : notnull where TCollection : class, ICollection> { - protected DictionaryWrapperBase(TCollection source, bool owner = false) - : base(source, owner) - { - } - /// public TValue this[TKey key] { @@ -21,13 +21,23 @@ public TValue this[TKey key] set => SetValueInternal(key, value); } + /// + /// Get the value for the key. + /// protected abstract TValue GetValueInternal(TKey key); + /// + /// Set the value for the key. + /// protected abstract void SetValueInternal(TKey key, TValue value); ICollection? _keys; /// public ICollection Keys => _keys ??= GetKeys(); + + /// + /// Get the keys. + /// protected abstract ICollection GetKeys(); ICollection? _values; @@ -35,8 +45,14 @@ public TValue this[TKey key] /// public ICollection Values => _values ??= GetValues(); + /// + /// Get the values. + /// protected abstract ICollection GetValues(); + /// + /// Add a key and value to the dictionary. + /// protected abstract void AddInternal(TKey key, TValue value); /// @@ -51,5 +67,10 @@ public void Add(TKey key, TValue value) public abstract bool Remove(TKey key); /// - public abstract bool TryGetValue(TKey key, out TValue value); + public abstract bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#else +#endif + out TValue value); } diff --git a/source/Extensions.Combinations.cs b/source/Extensions.Combinations.cs index 57fa121..b738b44 100644 --- a/source/Extensions.Combinations.cs +++ b/source/Extensions.Combinations.cs @@ -160,13 +160,13 @@ public static IEnumerable Combinations(this IEnumerable elements, int Contract.EndContractBlock(); if (length == 0) return Enumerable.Empty(); - IReadOnlyList? source = elements as IReadOnlyList ?? elements.ToArray(); + IReadOnlyList source = elements as IReadOnlyList ?? elements.ToArray(); int count = source.Count; return count == 0 ? Enumerable.Empty() : uniqueOnly ? source.Subsets(length) - : CombinationsCore(source, length, true).Select(e => e.Array.AsCopy(length)); + : CombinationsCore(source, length, true).Select(e => e.Array!.ToArrayOfLength(length)); } /// diff --git a/source/Extensions.ConcurrentDictionary.cs b/source/Extensions.ConcurrentDictionary.cs index b0d1d7a..a3c347e 100644 --- a/source/Extensions.ConcurrentDictionary.cs +++ b/source/Extensions.ConcurrentDictionary.cs @@ -11,6 +11,7 @@ public static partial class Extensions /// Shortcut for removeing a value without needing an 'out' parameter. /// public static bool TryRemove(this ConcurrentDictionary target, TKey key) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); @@ -28,6 +29,7 @@ public static TValue GetOrAdd( out bool updated, TKey key, Func valueFactory) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -59,6 +61,7 @@ public static TValue GetOrAdd( out bool updated, TKey key, TValue value) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -81,7 +84,10 @@ public static TValue GetOrAdd( /// /// Will return true if the existing value is past due. /// - public static bool UpdateRequired(this ConcurrentDictionary source, TKey key, TimeSpan timeBeforeExpires) + public static bool UpdateRequired( + this ConcurrentDictionary source, TKey key, TimeSpan timeBeforeExpires) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -113,6 +119,7 @@ public static Lazy GetOrAddSafely( this ConcurrentDictionary> source, TKey key, Func valueFactory) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -144,6 +151,7 @@ public static Lazy> GetOrAddSafely( this ConcurrentDictionary>> source, TKey key, Func> valueFactory) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); diff --git a/source/Extensions.Generic.Synchronized.cs b/source/Extensions.Generic.Synchronized.cs index eade5b2..6c7bd9d 100644 --- a/source/Extensions.Generic.Synchronized.cs +++ b/source/Extensions.Generic.Synchronized.cs @@ -23,13 +23,18 @@ internal static void ValidateMillisecondsTimeout(int? millisecondsTimeout) /// True if a value was acquired. public static bool TryGetValueSynchronized( this IDictionary target, - TKey key, out TValue value) + TKey key, +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out TValue value) { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); - TValue result = default!; + TValue? result = default; bool success = ThreadSafety.SynchronizeRead(target, key, () => ThreadSafety.SynchronizeRead(target, () => target.TryGetValue(key, out result) @@ -44,8 +49,8 @@ public static bool TryGetValueSynchronized( /// /// Attempts to acquire a specified type from a generic dictonary. /// - [SuppressMessage("Style", "IDE0046:Convert to conditional expression")] - public static TValue GetValueSynchronized(this IDictionary target, TKey key, bool throwIfNotExists = true) + public static TValue GetValueSynchronized( + this IDictionary target, TKey key) { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -53,10 +58,22 @@ public static TValue GetValueSynchronized(this IDictionary + /// Attempts to acquire a specified type from a generic dictonary or returns a default value. + /// + public static TValue GetValueSynchronized( + this IDictionary target, TKey key, TValue defaultValue) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + if (key is null) throw new ArgumentNullException(nameof(key)); + Contract.EndContractBlock(); + + bool exists = target.TryGetValueSynchronized(key, out TValue? value); + + return exists ? value! : defaultValue; } /// @@ -159,6 +176,10 @@ public static T AddOrUpdateSynchronized(this IDictionary targe return valueUsed; } + /// + /// Thread safe shortcut for adding a value to list. + /// + /// public static void AddSynchronized(this ICollection target, T value) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -174,7 +195,7 @@ public static void AddToSynchronized(this IDictionary? list = c.GetOrAddSynchronized(key, _ => new List()); + IList? list = c.GetOrAddSynchronized(key, _ => []); list.AddSynchronized(value); } @@ -224,7 +245,7 @@ public static T GetOrAddSynchronized( ValidateMillisecondsTimeout(millisecondsTimeout); Contract.EndContractBlock(); - T result = default!; + T? result = default; bool condition(bool _) => !target.TryGetValue(key, out result); void render() @@ -236,7 +257,7 @@ void render() if (!ThreadSafety.SynchronizeReadWrite(target, condition, render, millisecondsTimeout, throwsOnTimeout)) return value; // Value doesn't exist and timeout exceeded? Return the add value... - return result; + return result!; } /// @@ -255,7 +276,7 @@ public static T GetOrAddSynchronized( ValidateMillisecondsTimeout(millisecondsTimeout); Contract.EndContractBlock(); - T result = default!; + T? result = default; // Note, the following sync read is on the TARGET and not the key. See below. bool condition(bool _) => !ThreadSafety.SynchronizeRead(target, () => target.TryGetValue(key, out result)); @@ -274,7 +295,7 @@ public static T GetOrAddSynchronized( // 5) The value is then rendered using the ensureRendered query without locking the entire collection. This allows for other values to be added. // 6) The rendered value is then used to add to the collection if the value is missing, locking the collection if an add is necessary. - return result; + return result!; } /// diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index a7b9a0c..e8cfaa1 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -38,7 +38,13 @@ public static void Register(this ICollection target, T value) target.Add(value); } - public static void AddRange(this ICollection target, IEnumerable values) + /// + /// Adds each value to the end of the collection. + /// + /// If the is null. + public static void AddRange( + this ICollection target, + IEnumerable values) { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); @@ -50,6 +56,9 @@ public static void AddRange(this ICollection target, IEnumerable values target.Add(value); } + /// + /// Adds each value to the end of the collection. + /// public static void AddThese(this ICollection target, T a, T b, params T[] more) { target.Add(a); @@ -58,6 +67,9 @@ public static void AddThese(this ICollection target, T a, T b, params T[] target.AddRange(more); } + /// + /// Removes each value from the collection. + /// public static int Remove(this ICollection target, IEnumerable values) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -156,7 +168,7 @@ public static void AddTo(this IDictionary> c, if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); - IList? list = c.GetOrAdd(key, _ => new List()); + IList? list = c.GetOrAdd(key, _ => []); list.Add(value); } diff --git a/source/Extensions.Stream.cs b/source/Extensions.Stream.cs index 57a75f8..dcd970f 100644 --- a/source/Extensions.Stream.cs +++ b/source/Extensions.Stream.cs @@ -11,10 +11,6 @@ public static partial class Extensions /// /// Copies the source stream to the target. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Reliability", - "CA2016:Forward the 'CancellationToken' parameter to methods", - Justification = "Is required to ensure recieved data is not lost.")] public static async ValueTask DualBufferCopyToAsync( this Stream source, Stream target, @@ -38,11 +34,11 @@ public static async ValueTask DualBufferCopyToAsync( if (n == 0) break; // Preemptive request before yielding. - Task? current = cancellationToken.IsCancellationRequested ? null : source.ReadAsync(cCurrent, 0, bufferSize); -#if NETSTANDARD2_1_OR_GREATER - await target.WriteAsync(cNext.AsMemory(0, n)).ConfigureAwait(false); + Task current = source.ReadAsync(cCurrent, 0, bufferSize, cancellationToken); +#if NETSTANDARD2_0 + await target.WriteAsync(cNext, 0, n, cancellationToken).ConfigureAwait(false); #else - await target.WriteAsync(cNext, 0, n).ConfigureAwait(false); + await target.WriteAsync(cNext.AsMemory(0, n), cancellationToken).ConfigureAwait(false); #endif if (current is null) throw new OperationCanceledException(); (cCurrent, cNext) = (cNext, cCurrent); diff --git a/source/Extensions.cs b/source/Extensions.cs index 2a6263e..e70ce0c 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Dynamic; using System.Linq; @@ -31,8 +32,8 @@ public static ExpandoObject ToExpando(this IEnumerable)expando; + ExpandoObject expando = new(); + var expandoDic = (IDictionary)expando!; // go through the items in the dictionary and copy over the key value pairs) @@ -75,6 +76,10 @@ public static ExpandoObject ToExpando(this IEnumerable + /// Clones the source 2D array into a new 2D array. + /// + /// The is null. public static T[,] BiClone(this T[,] source) { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -90,6 +95,10 @@ public static ExpandoObject ToExpando(this IEnumerable + /// Overwrites the target 2D array with the source 2D array. + /// + /// The or is null. public static void Overwrite(this T[,] source, T[,] target) { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -99,6 +108,10 @@ public static void Overwrite(this T[,] source, T[,] target) source.ForEach((x, y, value) => target[x, y] = value); } + /// + /// Iterates through the 2D array and executes the closure for each element. + /// + /// The or is null. public static void ForEach(this T[,] source, Action closure) { if (source is null) throw new ArgumentNullException(nameof(source)); @@ -117,45 +130,35 @@ public static void ForEach(this T[,] source, Action closure) } } - public static T[] AsCopy(this T[] source, int? length = null) + /// + /// Creates a copy of the source array with a new length. + /// + /// If the is less than the length, the copy will contain all elements up to that length. If is greater then there will be untouched elements after that. + /// The is null. + public static T[] ToArrayOfLength(this T[] source, int length) { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); - var newArray = new T[length ?? source.Length]; + var newArray = new T[length]; int len = Math.Min(newArray.Length, source.Length); for (int i = 0; i < len; i++) newArray[i] = source[i]; return newArray; } - public static IEnumerable> CopyEachUsing( - this IEnumerable> source, - ArrayPool pool) - { - if (source is null) throw new ArgumentNullException(nameof(source)); - if (pool is null) throw new ArgumentNullException(nameof(pool)); - Contract.EndContractBlock(); - - return CopyUsingCore(source, pool); - - static IEnumerable> CopyUsingCore(IEnumerable> source, ArrayPool pool) - { - foreach (ReadOnlyMemory item in source) - { - int len = item.Length; - T[]? a = pool.Rent(len); - item.CopyTo(a); - yield return new ArraySegment(a, 0, len); - } - } - } - - public static ICollection AsCollection(this IEnumerable source) + /// + /// Coerces to a collection either by matching the type or by creating a new array. + /// + public static ICollection ToCollection(this IEnumerable source) => source is null ? null! : source as ICollection ?? source.ToArray(); + /// + /// Iterates over the source in parallel. + /// + /// The or are null. public static void ForEach(this IEnumerable target, ParallelOptions? parallelOptions, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -175,13 +178,21 @@ public static void ForEach(this IEnumerable target, ParallelOptions? paral closure); } - public static void ForEach(this IEnumerable target, Action closure, ushort parallel) + /// + /// Iterates over the source in parallel. + /// + /// The or are null. + public static void ForEach(this IEnumerable target, Action closure, ushort maxConcurrency) => target.ForEach( - parallel == 0 + maxConcurrency == 0 ? null - : new ParallelOptions { MaxDegreeOfParallelism = parallel }, + : new ParallelOptions { MaxDegreeOfParallelism = maxConcurrency }, closure); + /// + /// Iterates over the source and optionally can do so in parallel. + /// + /// The or are null. public static void ForEach(this IEnumerable target, Action closure, bool allowParallel = false) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -200,6 +211,10 @@ public static void ForEach(this IEnumerable target, Action closure, boo closure(t); } + /// + /// Iterates over the source in parallel with a lock. + /// + /// The or are null. public static void ForEach(this ISynchronizedCollection target, ParallelOptions? parallelOptions, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -222,13 +237,21 @@ public static void ForEach(this ISynchronizedCollection target, ParallelOp }); } - public static void ForEach(this ISynchronizedCollection target, Action closure, ushort parallel) + /// + /// Iterates over the source in parallel with a lock. + /// + /// The or are null. + public static void ForEach(this ISynchronizedCollection target, Action closure, ushort maxConcurrency) => target.ForEach( - parallel == 0 + maxConcurrency == 0 ? null - : new ParallelOptions { MaxDegreeOfParallelism = parallel }, + : new ParallelOptions { MaxDegreeOfParallelism = maxConcurrency }, closure); + /// + /// Iterates over the source with a lock and optionally can do so in parallel. + /// + /// The or are null. public static void ForEach(this ISynchronizedCollection target, Action closure, bool allowParallel = false) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -250,7 +273,13 @@ public static void ForEach(this ISynchronizedCollection target, Action }); } + /// + /// Iterates over the source and can be cancelled. + /// + /// The or are null. +#pragma warning disable IDE0079 // Remove unnecessary suppression [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Allows for simpler implementation. Other methods cover non-cancellable case.")] +#pragma warning restore IDE0079 // Remove unnecessary suppression public static void ForEach(this IEnumerable target, CancellationToken token, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -264,7 +293,10 @@ public static void ForEach(this IEnumerable target, CancellationToken toke } } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Allows for simpler implementation. Other methods cover non-cancellable case.")] + /// + /// Iterates over the source with a lock and can be cancelled. + /// + /// The or are null. public static void ForEach(this ISynchronizedCollection target, CancellationToken token, Action closure) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -349,7 +381,7 @@ public static bool ConcurrentMoveNext(this IEnumerator source, Action t return false; } - static async Task PreCacheWorker(IEnumerator e, Channel queue) + static async Task PreCacheWorker(IEnumerator e, Channel queue, CancellationToken cancellationToken) { try { @@ -361,7 +393,7 @@ static async Task PreCacheWorker(IEnumerator e, Channel queue) retry: if (queue.Writer.TryWrite(value)) continue; - if (await queue.Writer.WaitToWriteAsync()) goto retry; + if (await queue.Writer.WaitToWriteAsync(cancellationToken).ConfigureAwait(false)) goto retry; break; } @@ -378,14 +410,14 @@ static async Task PreCacheWorker(IEnumerator e, Channel queue) /// /// Similar to a buffer but is loaded by another thread and attempts keep the buffer full while contents are being pulled. /// - public static IEnumerable PreCache(this IEnumerable source, int count = 1) + public static IEnumerable PreCache(this IEnumerable source, int count = 1, CancellationToken cancellationToken = default) { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); - return PreCacheCore(source, count); + return PreCacheCore(source, count, cancellationToken); - static IEnumerable PreCacheCore(IEnumerable source, int count) + static IEnumerable PreCacheCore(IEnumerable source, int count, CancellationToken cancellationToken) { if (count <= 0) { @@ -402,7 +434,7 @@ static IEnumerable PreCacheCore(IEnumerable source, int count) else yield break; // Queue up. - PreCacheWorker(e, queue).ConfigureAwait(false); + PreCacheWorker(e, queue, cancellationToken).ConfigureAwait(false); // Dequeue into the enumerable. do @@ -410,12 +442,12 @@ static IEnumerable PreCacheCore(IEnumerable source, int count) while (queue.Reader.TryRead(out T? item)) yield return item; } - while (queue.Reader.WaitToReadAsync().AsTask().Result); + while (queue.Reader.WaitToReadAsync(cancellationToken).AsTask().Result); - Task? complete = queue.Reader.Completion; + Task complete = queue.Reader.Completion; if (complete.IsFaulted) { - throw complete.Exception.InnerException; + throw complete.Exception.InnerException ?? complete.Exception; } } } @@ -517,6 +549,7 @@ public static string JoinToString(this IEnumerable source, string separato }*/ public static Dictionary ToDictionary(this ParallelQuery> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -525,6 +558,7 @@ public static Dictionary ToDictionary(this ParallelQ } public static Dictionary ToDictionary(this IEnumerable> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -533,6 +567,7 @@ public static Dictionary ToDictionary(this IEnumerab } public static SortedDictionary ToSortedDictionary(this IEnumerable> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -546,6 +581,7 @@ public static SortedDictionary ToSortedDictionary(th public static SortedDictionary ToSortedDictionary(this IEnumerable source, Func keySelector, Func valueSelector) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); if (keySelector is null) throw new ArgumentNullException(nameof(keySelector)); @@ -560,6 +596,7 @@ public static SortedDictionary ToSortedDictionary> ToSortedDictionary(this IEnumerable> source) + where TKey : notnull { if (source is null) throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); @@ -649,7 +686,7 @@ public static string[] ToStringArray(this IEnumerable list) if (list is null) throw new ArgumentNullException(nameof(list)); Contract.EndContractBlock(); - return list.Select(r => r!.ToString()).ToArray(); + return list.Select(r => r!.ToString() ?? "null").ToArray(); } /// @@ -748,8 +785,9 @@ private static IQueryable ApplyOrderBy(IQueryable collection, OrderByIn object? result = r1 .MakeGenericMethod(typeof(T), type) - .Invoke(null, new object[] { collection, lambda }); + .Invoke(null, [collection, lambda]); + Debug.Assert(result is not null); return (IOrderedQueryable)result; } @@ -913,17 +951,17 @@ public static LinkedListNode GetNodeAt(this LinkedList list, int index) if (index < count / 2) { // Start from the beginning - current = list.First; + current = list.First!; for (int i = 0; i < index; i++) - current = current.Next; + current = current.Next!; return current; } // Start from the end - current = list.Last; + current = list.Last!; for (int i = count - 1; i > index; i--) - current = current.Previous; + current = current.Previous!; return current; } @@ -969,22 +1007,11 @@ public static Span CopyToSpan(this IEnumerable source, Span target) /// /// Builds an immutable array using the contents of the span. /// - public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) - { - ImmutableArray.Builder? builder = ImmutableArray.CreateBuilder(span.Length); - foreach (T? e in span) - builder.Add(e); - return builder.MoveToImmutable(); - } + public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) => [.. span]; /// public static ImmutableArray ToImmutableArray(this Span span) - { - ImmutableArray.Builder? builder = ImmutableArray.CreateBuilder(span.Length); - foreach (T? e in span) - builder.Add(e); - return builder.MoveToImmutable(); - } + => [.. span]; /// /// Builds an immutable array using the contents of the memory. @@ -1045,17 +1072,15 @@ public static IEnumerable BeforeGetEnumerator( Action before) => new PreflightEnumerable(source, before); - private class PreflightEnumerable : IEnumerable + private class PreflightEnumerable( + IEnumerable source, Action before) + : IEnumerable { - private readonly IEnumerable _source; - private readonly Action _before; + private readonly IEnumerable _source = source ?? throw new ArgumentNullException(nameof(source)); - public PreflightEnumerable(IEnumerable source, Action before) - { - _source = source ?? throw new ArgumentNullException(nameof(source)); - _before = before ?? throw new ArgumentNullException(nameof(before)); - } + private readonly Action _before = before ?? throw new ArgumentNullException(nameof(before)); + /// public IEnumerator GetEnumerator() { _before(); diff --git a/source/IndexedDictionary.cs b/source/IndexedDictionary.cs index bbeb2c9..24dec00 100644 --- a/source/IndexedDictionary.cs +++ b/source/IndexedDictionary.cs @@ -12,11 +12,15 @@ namespace Open.Collections; /// public class IndexedDictionary : DictionaryWrapper, IIndexedDictionary + where TKey : notnull { private const string OUTOFSYNC = "Collection is out of sync possibly due to unsynchronized access by multiple threads."; private readonly List> _entries; private readonly Dictionary _indexes; + /// + /// Constructs a new instance with the specified capacity. + /// [ExcludeFromCodeCoverage] public IndexedDictionary(int capacity) : base(capacity) @@ -25,13 +29,16 @@ public IndexedDictionary(int capacity) _indexes = new(capacity); } + /// + /// Constructs a new instance. + /// public IndexedDictionary() - : base() { - _entries = new(); - _indexes = new(); + _entries = []; + _indexes = []; } + /// protected override void OnDispose() { base.OnDispose(); @@ -39,6 +46,9 @@ protected override void OnDispose() _indexes.Clear(); } + /// + /// Sets the value for the key. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void SetValueInternal(TKey key, TValue value) => SetValue(key, value); @@ -46,12 +56,14 @@ protected override void OnDispose() public override IEnumerator> GetEnumerator() => _entries.GetEnumerator().Preflight(ThrowIfDisposedDelegate); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override ICollection GetKeys() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(_entries.Select(e => e.Key)), () => _entries.Count); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override ICollection GetValues() => new ReadOnlyCollectionAdapter( @@ -82,6 +94,7 @@ private int AddToLists(in KeyValuePair kvp) return i; } + /// [ExcludeFromCodeCoverage] [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(in KeyValuePair item) @@ -98,6 +111,7 @@ protected override void AddInternal(in KeyValuePair item) return AddToLists(in kvp); } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(TKey key, TValue value) => Add(key, value); @@ -157,6 +171,7 @@ private void RemoveIndex(int index, TKey key) Debug.Assert(_entries.Count == InternalSource.Count); } + /// public override void Clear() { base.Clear(); diff --git a/source/ItemChangedEventArgs.cs b/source/ItemChangedEventArgs.cs index cf3981c..0e2455e 100644 --- a/source/ItemChangedEventArgs.cs +++ b/source/ItemChangedEventArgs.cs @@ -14,25 +14,20 @@ public enum ItemChange Modified } -public class ItemChangedEventArgs : EventArgs +public class ItemChangedEventArgs( + ItemChange action, T value, int version) + : EventArgs { - public readonly ItemChange Change; - public readonly T Value; - public readonly int Version; - - public ItemChangedEventArgs(ItemChange action, T value, int version) - { - Change = action; - Value = value; - Version = version; - } + public readonly ItemChange Change = action; + public readonly T Value = value; + public readonly int Version = version; } -public class ItemChangedEventArgs : ItemChangedEventArgs +public class ItemChangedEventArgs( + ItemChange action, TIndex index, TValue value, int version) + : ItemChangedEventArgs(action, value, version) { - public readonly TIndex Index; - public ItemChangedEventArgs(ItemChange action, TIndex index, TValue value, int version) - : base(action, value, version) => Index = index; + public readonly TIndex Index = index; } public static class ItemChangeEventArgs diff --git a/source/LazyList.cs b/source/LazyList.cs index 0835796..4441323 100644 --- a/source/LazyList.cs +++ b/source/LazyList.cs @@ -15,23 +15,20 @@ namespace Open.Collections; /// A a thread-safe list for caching the results of an enumerable. /// Note: should be disposed manually whenever possible as the locking mechanism is a ReaderWriterLockSlim. /// -public sealed class LazyList : LazyListUnsafe +public sealed class LazyList( + IEnumerable source, bool isEndless = false) + : LazyListUnsafe(source) { - ReaderWriterLockSlim Sync; + ReaderWriterLockSlim Sync = new(LockRecursionPolicy.NoRecursion); int _safeCount; /// /// A value indicating whether the results are known or expected to be finite. /// A list that was constructed as endless but has reached the end of the results will return false. /// - public bool IsEndless { get; private set; } - - public LazyList(IEnumerable source, bool isEndless = false) : base(source) - { - Sync = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); // This is important as it's possible to recurse infinitely to generate a result. :( - IsEndless = isEndless; // To indicate if a source is not allowed to fully enumerate. - } + public bool IsEndless { get; private set; } = isEndless; // To indicate if a source is not allowed to fully enumerate. + /// protected override void OnDispose() { using (Sync.WriteLock()) base.OnDispose(); @@ -51,6 +48,7 @@ public override int IndexOf(T item) return base.IndexOf(item); } + /// protected override bool EnsureIndex(int maxIndex) { if (maxIndex < _safeCount) @@ -59,7 +57,7 @@ protected override bool EnsureIndex(int maxIndex) // This is where the fun begins... // Mutliple threads can be out of sync (probably through a memory barrier) // And a sync read operation must be done to ensure safety. - int count = Sync.Read(() => _cached.Count); + int count = Sync.Read(() => Cached.Count); if (maxIndex < count) { // We're still within the existing results, but safe count is not up to date. @@ -70,42 +68,43 @@ protected override bool EnsureIndex(int maxIndex) return true; } - if (_enumerator is null) + if (Enumerator is null) return false; // This very well could be a simple lock{} statement but the ReaderWriterLockSlim recursion protection is actually quite useful. using var uLock = Sync.UpgradableReadLock(); // Note: Within an upgradable read, other reads pile up. - int c = _cached.Count; + int c = Cached.Count; if (_safeCount != c) // Always do comparisons outside of interlocking first. Interlocked.CompareExchange(ref _safeCount, c, _safeCount); if (maxIndex < _safeCount) return true; - if (_enumerator is null) + if (Enumerator is null) return false; using var wLock = Sync.WriteLock(); - while (_enumerator.MoveNext()) + while (Enumerator.MoveNext()) { - if (_cached.Count == int.MaxValue) + if (Cached.Count == int.MaxValue) throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); - _cached.Add(_enumerator.Current); + Cached.Add(Enumerator.Current); - if (maxIndex < _cached.Count) + if (maxIndex < Cached.Count) return true; } IsEndless = false; - DisposeOf(ref _enumerator); + DisposeOf(ref Enumerator); return false; } + /// protected override void Finish() { if (IsEndless) diff --git a/source/LazyListUnsafe.cs b/source/LazyListUnsafe.cs index e8f1449..a532da1 100644 --- a/source/LazyListUnsafe.cs +++ b/source/LazyListUnsafe.cs @@ -16,21 +16,24 @@ namespace Open.Collections; /// Only use if you know the results are finite and access is thread safe. /// Note: disposing releases the underlying enumerator if it never reached the end of the results. /// -public class LazyListUnsafe : DisposableBase, IReadOnlyList +public class LazyListUnsafe(IEnumerable source) + : DisposableBase, IReadOnlyList { - protected List _cached; - protected IEnumerator _enumerator; + /// + /// The memoized results. + /// + protected List Cached = []; - public LazyListUnsafe(IEnumerable source) - { - _enumerator = source.GetEnumerator(); - _cached = new List(); - } + /// + /// The enumerator for the source. + /// + protected IEnumerator Enumerator = source.GetEnumerator(); + /// protected override void OnDispose() { - DisposeOf(ref _enumerator); - Nullify(ref _cached)?.Clear(); + DisposeOf(ref Enumerator); + Nullify(ref Cached)?.Clear(); } const string MUST_BE_AT_LEAST_ZERO = "Must be at least zero."; @@ -49,7 +52,7 @@ public T this[int index] throw new ArgumentOutOfRangeException(nameof(index), GREATER_THAN_TOTAL); Contract.EndContractBlock(); - return _cached[index]; + return Cached[index]; } } @@ -60,7 +63,7 @@ public int Count { AssertIsAlive(); Finish(); - return _cached.Count; + return Cached.Count; } } @@ -77,7 +80,7 @@ public bool TryGetValueAt(int index, out T value) if (EnsureIndex(index)) { - value = _cached[index]; + value = Cached[index]; return true; } @@ -103,7 +106,7 @@ public virtual int IndexOf(T item) for (int index = 0; EnsureIndex(index); index++) { - T? value = _cached[index]; + T? value = Cached[index]; if (value is null) { if (item is null) return index; @@ -137,30 +140,36 @@ public Span CopyTo(T[] array, int startIndex = 0) System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Ensures that the index is available by enumerating to that index and memoizing results. + /// protected virtual bool EnsureIndex(int maxIndex) { - if (maxIndex < _cached.Count) + if (maxIndex < Cached.Count) return true; - if (_enumerator is null) + if (Enumerator is null) return false; - while (_enumerator.MoveNext()) + while (Enumerator.MoveNext()) { - if (_cached.Count == int.MaxValue) + if (Cached.Count == int.MaxValue) throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); - _cached.Add(_enumerator.Current); + Cached.Add(Enumerator.Current); - if (maxIndex < _cached.Count) + if (maxIndex < Cached.Count) return true; } - DisposeOf(ref _enumerator); + DisposeOf(ref Enumerator); return false; } + /// + /// Ensures all results are memoized. + /// protected virtual void Finish() { while (EnsureIndex(int.MaxValue)) { } diff --git a/source/LinkedList/ILinkedList.cs b/source/LinkedList/ILinkedList.cs index 8753ada..3ca042d 100644 --- a/source/LinkedList/ILinkedList.cs +++ b/source/LinkedList/ILinkedList.cs @@ -2,13 +2,16 @@ namespace Open.Collections; +/// +/// An interface for a linked list. +/// public interface ILinkedList : ICollection { /// - LinkedListNode First { get; } + LinkedListNode? First { get; } /// - LinkedListNode Last { get; } + LinkedListNode? Last { get; } /// LinkedListNode AddAfter(LinkedListNode node, T item); diff --git a/source/ListWrapper.cs b/source/ListWrapper.cs index c465d07..fb5dacd 100644 --- a/source/ListWrapper.cs +++ b/source/ListWrapper.cs @@ -3,16 +3,12 @@ using System.Runtime.CompilerServices; namespace Open.Collections; -public class ListWrapper - : CollectionWrapper, IList + +public class ListWrapper( + TList source, bool owner = false) + : CollectionWrapper(source, owner), IList where TList : class, IList { - [ExcludeFromCodeCoverage] - public ListWrapper(TList source, bool owner = false) - : base(source, owner) - { - } - /// [ExcludeFromCodeCoverage] public virtual T this[int index] diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index f76121b..46bd795 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net9.0 latest enable true @@ -26,10 +26,11 @@ snupkg logo.png README.md + IDE0130;CA1510;CA1068;IDE0305;IDE0301;RCS1196; - + @@ -50,13 +51,18 @@ - - - + + + - + + + + $(NoWarn);nullable + + \ No newline at end of file diff --git a/source/OrderedDictionary.cs b/source/OrderedDictionary.cs index fc47464..6c5ea93 100644 --- a/source/OrderedDictionary.cs +++ b/source/OrderedDictionary.cs @@ -7,14 +7,19 @@ namespace Open.Collections; +/// +/// A dictionary that maintains the order of items as they are added +/// and can be accessed by index. +/// public class OrderedDictionary : DictionaryWrapperBase>>, IDictionary + where TKey : notnull { /// [ExcludeFromCodeCoverage] public OrderedDictionary() : base(new LinkedList>(), true) - => _lookup = new(); + => _lookup = []; /// [ExcludeFromCodeCoverage] @@ -23,9 +28,14 @@ public OrderedDictionary(int capacity) => _lookup = new(capacity); private Dictionary>> _lookup; + + /// + /// The dictionary that can look up the node for a key. + /// protected Dictionary>> Lookup => _lookup ?? throw new ObjectDisposedException(GetType().ToString()); + /// [ExcludeFromCodeCoverage] protected override void OnDispose() { @@ -34,17 +44,22 @@ protected override void OnDispose() base.OnDispose(); } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override TValue GetValueInternal(TKey key) => Lookup[key].Value.Value; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void SetValueInternal(TKey key, TValue value) => SetValue(key, value); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(in KeyValuePair kvp) => AddNode(in kvp); - /// + /// + /// Updates a value by key and returns true if the value changed. + /// public virtual bool SetValue(TKey key, TValue value) { AssertIsAlive(); @@ -61,11 +76,13 @@ public virtual bool SetValue(TKey key, TValue value) return true; } + /// protected override ICollection GetKeys() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(InternalSource.Select(e => e.Key)), () => InternalSource.Count); + /// protected override ICollection GetValues() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(InternalSource.Select(e => e.Value)), @@ -76,6 +93,9 @@ protected override ICollection GetValues() protected override void AddInternal(TKey key, TValue value) => AddInternal(KeyValuePair.Create(key, value)); + /// + /// Adds a node to the lookup dictionary. + /// #if NETSTANDARD2_0 [SuppressMessage("Roslynator", "RCS1242:Do not pass non-read-only struct by read-only reference.", Justification = "KeyValuePairs are not truly readonly until NET Standard 2.1.")] #endif @@ -129,13 +149,14 @@ public override void Clear() base.Clear(); } + /// public override bool Remove(KeyValuePair item) { var key = item.Key; if (Lookup.TryGetValue(key, out var node) && (node.Value.Value?.Equals(item.Value) ?? item.Value is null)) { - Debug.Assert(key?.Equals(node.Value.Key) ?? node.Value.Key is null); + Debug.Assert(key.Equals(node.Value.Key)); bool removed = Lookup.Remove(key); Debug.Assert(removed); InternalSource.Remove(node); diff --git a/source/Queue/IQueue.cs b/source/Queue/IQueue.cs index 742208a..2b1f57c 100644 --- a/source/Queue/IQueue.cs +++ b/source/Queue/IQueue.cs @@ -1,4 +1,6 @@ -namespace Open.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace Open.Collections; public interface IQueue { @@ -6,10 +8,20 @@ public interface IQueue void Enqueue(T item); /// - bool TryDequeue(out T item); + bool TryDequeue( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item); /// - bool TryPeek(out T item); + bool TryPeek( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item); /// int Count { get; } diff --git a/source/Queue/Queue.Standard.cs b/source/Queue/Queue.Standard.cs index 8dd7554..50a11ac 100644 --- a/source/Queue/Queue.Standard.cs +++ b/source/Queue/Queue.Standard.cs @@ -3,15 +3,27 @@ namespace Open.Collections; +/// +/// Static collection of queue implementations. +/// public static partial class Queue { + /// + /// A standard queue implementation that is based upon . + /// public class Standard : Queue, IQueue { + /// + /// Construct an empty queue. + /// [ExcludeFromCodeCoverage] - protected Standard() + public Standard() { } + /// + /// Construct a queue with an initial set of items. + /// [ExcludeFromCodeCoverage] public Standard(IEnumerable initial) : base(initial) { @@ -38,12 +50,16 @@ public virtual bool TryPeek(out T item) /// [ExcludeFromCodeCoverage] - public new virtual bool TryDequeue(out T item) + public new virtual bool TryDequeue( + [MaybeNullWhen(false)] + out T item) => base.TryDequeue(out item); /// [ExcludeFromCodeCoverage] - public new virtual bool TryPeek(out T item) + public new virtual bool TryPeek( + [MaybeNullWhen(false)] + out T item) => base.TryPeek(out item); #endif diff --git a/source/ReadOnlyCollectionAdapter.cs b/source/ReadOnlyCollectionAdapter.cs index 5413294..739345f 100644 --- a/source/ReadOnlyCollectionAdapter.cs +++ b/source/ReadOnlyCollectionAdapter.cs @@ -7,22 +7,16 @@ namespace Open.Collections; -public sealed class ReadOnlyCollectionAdapter +[method: ExcludeFromCodeCoverage] +public sealed class ReadOnlyCollectionAdapter( + IEnumerable source, Func getCount) : IReadOnlyCollection, ICollection { - readonly IEnumerable _source; - readonly Func _getCount; - readonly Func _contains; - - [ExcludeFromCodeCoverage] - public ReadOnlyCollectionAdapter(IEnumerable source, Func getCount) - { - _source = source ?? throw new ArgumentNullException(nameof(source)); - _getCount = getCount ?? throw new ArgumentNullException(nameof(getCount)); - _contains = source is ICollection c + readonly IEnumerable _source = source ?? throw new ArgumentNullException(nameof(source)); + readonly Func _getCount = getCount ?? throw new ArgumentNullException(nameof(getCount)); + readonly Func _contains = source is ICollection c ? item => c.Contains(item) : item => source.Contains(item); - } [ExcludeFromCodeCoverage] public ReadOnlyCollectionAdapter(IReadOnlyCollection source) diff --git a/source/ReadOnlyCollectionWrapper.cs b/source/ReadOnlyCollectionWrapper.cs index 412c83f..ae87d2a 100644 --- a/source/ReadOnlyCollectionWrapper.cs +++ b/source/ReadOnlyCollectionWrapper.cs @@ -7,13 +7,27 @@ namespace Open.Collections; +/// +/// A disposable aware read-only wrapper for a collection. +/// public class ReadOnlyCollectionWrapper : DisposableBase, IReadOnlyCollection where TCollection : class, ICollection { + /// + /// The underlying collection. + /// protected TCollection? InternalUnsafeSource; + + /// + /// If , will call if the source is when this is disposed. + /// protected readonly bool SourceOwned; + /// + /// The underlying collection. + /// + /// If this has been disposed. protected TCollection InternalSource => InternalUnsafeSource ?? throw new ObjectDisposedException(GetType().ToString()); @@ -27,6 +41,7 @@ protected TCollection InternalSource /// /// If the is . [ExcludeFromCodeCoverage] + [SuppressMessage("Style", "IDE0290:Use primary constructor")] public ReadOnlyCollectionWrapper(TCollection source, bool owner = false) { InternalUnsafeSource = source ?? throw new ArgumentNullException(nameof(source)); @@ -36,13 +51,23 @@ public ReadOnlyCollectionWrapper(TCollection source, bool owner = false) private void ThrowIfDisposedInternal() => base.AssertIsAlive(); private Action? _throwIfDisposed; + + /// + /// A delegate to throw an exception if this has been disposed. + /// protected Action ThrowIfDisposedDelegate => _throwIfDisposed ??= ThrowIfDisposedInternal; + /// + /// Produces an enumerable that will throw an exception if this has been disposed. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected IEnumerable ThrowIfDisposed(IEnumerable source) => source.Preflight(ThrowIfDisposedDelegate).BeforeGetEnumerator(ThrowIfDisposedDelegate); + /// + /// A utility for ensuring the source is not disposed. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected T2 ThrowIfDisposed(T2 source) { diff --git a/source/Synchronized/ConcurrentList.cs b/source/Synchronized/ConcurrentList.cs index fcf865b..8051e81 100644 --- a/source/Synchronized/ConcurrentList.cs +++ b/source/Synchronized/ConcurrentList.cs @@ -16,6 +16,8 @@ namespace Open.Collections.Synchronized; public sealed class ConcurrentList : ListWrapper>, ISynchronizedCollection { int _count; + + /// [ExcludeFromCodeCoverage] public override int Count { @@ -29,6 +31,7 @@ public override int Count private readonly Queue.Concurrent _buffer = new(); private readonly ReaderWriterLockSlim RWLock = new(); + /// [ExcludeFromCodeCoverage] protected override void OnDispose() { @@ -74,6 +77,9 @@ private List Grow() return list; } + /// + /// Gets or sets the capacity of the list. + /// public int Capacity { get => InternalSource.Capacity; @@ -84,11 +90,17 @@ public int Capacity } } + /// + /// Constructs a new instance with the specified capacity. + /// [ExcludeFromCodeCoverage] public ConcurrentList(int capacity) : base(new List(capacity)) { } + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] - public ConcurrentList() : base(new List()) { } + public ConcurrentList() : base([]) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AssertValidIndex(int index) @@ -111,6 +123,7 @@ public override T this[int index] } } + /// protected override void AddInternal(in T item) { _buffer.Enqueue(item); diff --git a/source/Synchronized/LockSynchronizedCollectionWrapper.cs b/source/Synchronized/LockSynchronizedCollectionWrapper.cs index 517c302..41a24e3 100644 --- a/source/Synchronized/LockSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/LockSynchronizedCollectionWrapper.cs @@ -7,13 +7,15 @@ namespace Open.Collections.Synchronized; -public class LockSynchronizedCollectionWrapper - : CollectionWrapper, ISynchronizedCollectionWrapper - where TCollection : class, ICollection +/// +/// A disposable synchronized wrapper for a collection. +/// +public class LockSynchronizedCollectionWrapper( + TCollection source, bool owner = false) + : CollectionWrapper(source, owner), ISynchronizedCollectionWrapper + where TCollection : class, ICollection { - protected LockSynchronizedCollectionWrapper(TCollection source, bool owner = false) - : base(source, owner) { } - + /// protected override void OnBeforeDispose() { ThreadSafety.Lock(Sync, () => { }, 1000); diff --git a/source/Synchronized/LockSynchronizedDictionaryWrapper.cs b/source/Synchronized/LockSynchronizedDictionaryWrapper.cs index ef44733..7f4d809 100644 --- a/source/Synchronized/LockSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/LockSynchronizedDictionaryWrapper.cs @@ -4,15 +4,11 @@ namespace Open.Collections.Synchronized; -/// [ExcludeFromCodeCoverage] -public class LockSynchronizedDictionaryWrapper - : LockSynchronizedCollectionWrapper, TDictionary>, IDictionary +public class LockSynchronizedDictionaryWrapper(TDictionary dictionary) + : LockSynchronizedCollectionWrapper, TDictionary>(dictionary), IDictionary where TDictionary : class, IDictionary { - /// - public LockSynchronizedDictionaryWrapper(TDictionary dictionary) : base(dictionary) { } - /// public virtual TValue this[TKey key] { @@ -64,22 +60,25 @@ public virtual bool Remove(TKey key) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(TKey key, out TValue value) + public bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#endif + out TValue value) => InternalSource.TryGetValue(key, out value); } [ExcludeFromCodeCoverage] -public class LockSynchronizedDictionaryWrapper - : LockSynchronizedDictionaryWrapper> +public class LockSynchronizedDictionaryWrapper( + IDictionary dictionary) + : LockSynchronizedDictionaryWrapper>(dictionary) { - public LockSynchronizedDictionaryWrapper(IDictionary dictionary) : base(dictionary) - { - } } [ExcludeFromCodeCoverage] public class LockSynchronizedDictionary : LockSynchronizedDictionaryWrapper + where TKey : notnull { public LockSynchronizedDictionary(int capacity) : base(new Dictionary(capacity)) { } public LockSynchronizedDictionary() : base(new Dictionary()) { } diff --git a/source/Synchronized/LockSynchronizedHashSet.cs b/source/Synchronized/LockSynchronizedHashSet.cs index 956c6c7..b1d413b 100644 --- a/source/Synchronized/LockSynchronizedHashSet.cs +++ b/source/Synchronized/LockSynchronizedHashSet.cs @@ -4,14 +4,26 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized . +/// public sealed class LockSynchronizedHashSet : LockSynchronizedCollectionWrapper>, ISet { + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] - public LockSynchronizedHashSet() : base(new HashSet()) { } + public LockSynchronizedHashSet() : base([]) { } + /// + /// Constructs a new instance with the specified capacity. + /// [ExcludeFromCodeCoverage] public LockSynchronizedHashSet(IEnumerable collection) : base(new HashSet(collection)) { } + /// + /// Constructs a new instance with the specified capacity and comparer. + /// [ExcludeFromCodeCoverage] public LockSynchronizedHashSet(IEnumerable collection, IEqualityComparer comparer) : base(new HashSet(collection, comparer)) { } diff --git a/source/Synchronized/LockSynchronizedIndexedDictionary.cs b/source/Synchronized/LockSynchronizedIndexedDictionary.cs index cec792a..551f59b 100644 --- a/source/Synchronized/LockSynchronizedIndexedDictionary.cs +++ b/source/Synchronized/LockSynchronizedIndexedDictionary.cs @@ -2,15 +2,11 @@ namespace Open.Collections.Synchronized; -/// [ExcludeFromCodeCoverage] // Nothing worth covering here yet. -public sealed class LockSynchronizedIndexedDictionary - : LockSynchronizedDictionaryWrapper>, IIndexedDictionary +public sealed class LockSynchronizedIndexedDictionary(int capacity = 0) + : LockSynchronizedDictionaryWrapper>(new IndexedDictionary(capacity)), IIndexedDictionary + where TKey : notnull { - /// - public LockSynchronizedIndexedDictionary(int capacity = 0) - : base(new IndexedDictionary(capacity)) { } - /// public TKey GetKeyAt(int index) => InternalSource.GetKeyAt(index); diff --git a/source/Synchronized/LockSynchronizedLinkedList.cs b/source/Synchronized/LockSynchronizedLinkedList.cs index 9ef7751..c5f7d77 100644 --- a/source/Synchronized/LockSynchronizedLinkedList.cs +++ b/source/Synchronized/LockSynchronizedLinkedList.cs @@ -15,12 +15,12 @@ public LockSynchronizedLinkedList(IEnumerable collection) : base(new LinkedLi /// [ExcludeFromCodeCoverage] - public LinkedListNode First + public LinkedListNode? First => InternalSource.First; /// [ExcludeFromCodeCoverage] - public LinkedListNode Last + public LinkedListNode? Last => InternalSource.Last; /// diff --git a/source/Synchronized/LockSynchronizedList.cs b/source/Synchronized/LockSynchronizedList.cs index 55dfd58..1172a5f 100644 --- a/source/Synchronized/LockSynchronizedList.cs +++ b/source/Synchronized/LockSynchronizedList.cs @@ -3,11 +3,25 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized list. +/// [ExcludeFromCodeCoverage] public sealed class LockSynchronizedList : LockSynchronizedListWrapper { - public LockSynchronizedList() : base(new List()) { } + /// + /// Constructs a new instance. + /// + public LockSynchronizedList() : base([]) { } + + /// + /// Constructs a new instance with the specified capacity. + /// public LockSynchronizedList(int capacity = 0) : base(new List(capacity)) { } + + /// + /// Constructs a new instance with the specified collection. + /// public LockSynchronizedList(IEnumerable collection) : base(new List(collection)) { } } diff --git a/source/Synchronized/LockSynchronizedListWrapper.cs b/source/Synchronized/LockSynchronizedListWrapper.cs index 5334df8..33d0d15 100644 --- a/source/Synchronized/LockSynchronizedListWrapper.cs +++ b/source/Synchronized/LockSynchronizedListWrapper.cs @@ -4,12 +4,11 @@ namespace Open.Collections.Synchronized; [ExcludeFromCodeCoverage] -public class LockSynchronizedListWrapper - : LockSynchronizedCollectionWrapper, IList +public class LockSynchronizedListWrapper( + TList list, bool owner = false) + : LockSynchronizedCollectionWrapper(list, owner), IList where TList : class, IList { - public LockSynchronizedListWrapper(TList list, bool owner = false) : base(list, owner) { } - // This is a simplified version. // It could be possible to allow indexed values to change independently of one another. // If that fine grained of read-write control is necessary, then use the ThreadSafety utility and extensions. @@ -41,10 +40,8 @@ public void RemoveAt(int index) } [ExcludeFromCodeCoverage] -public class LockSynchronizedListWrapper - : LockSynchronizedListWrapper> +public class LockSynchronizedListWrapper( + IList list, bool owner = false) + : LockSynchronizedListWrapper>(list, owner) { - public LockSynchronizedListWrapper(IList list, bool owner = false) : base(list, owner) - { - } } \ No newline at end of file diff --git a/source/Synchronized/LockSynchronizedOrderedDictionary.cs b/source/Synchronized/LockSynchronizedOrderedDictionary.cs index 10c2e3b..668e092 100644 --- a/source/Synchronized/LockSynchronizedOrderedDictionary.cs +++ b/source/Synchronized/LockSynchronizedOrderedDictionary.cs @@ -2,12 +2,10 @@ namespace Open.Collections.Synchronized; -/// [ExcludeFromCodeCoverage] -public sealed class LockSynchronizedOrderedDictionary - : LockSynchronizedDictionaryWrapper> +public sealed class LockSynchronizedOrderedDictionary( + int capacity = 0) + : LockSynchronizedDictionaryWrapper>(new OrderedDictionary(capacity)) + where TKey : notnull { - /// - public LockSynchronizedOrderedDictionary(int capacity = 0) - : base(new OrderedDictionary(capacity)) { } } diff --git a/source/Synchronized/LockSynchronizedQueue.cs b/source/Synchronized/LockSynchronizedQueue.cs index 4d95d2e..af6c4eb 100644 --- a/source/Synchronized/LockSynchronizedQueue.cs +++ b/source/Synchronized/LockSynchronizedQueue.cs @@ -17,7 +17,12 @@ public class LockSynchronizedQueue : Queue.Standard, IQueue } /// - public override bool TryDequeue(out T item) + public override bool TryDequeue( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item) { if (Count == 0) { @@ -30,7 +35,12 @@ public override bool TryDequeue(out T item) } /// - public override bool TryPeek(out T item) + public override bool TryPeek( +#if NETSTANDARD2_0 +#else + [MaybeNullWhen(false)] +#endif + out T item) { if (Count == 0) { diff --git a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs index 44c1aff..f0f55f5 100644 --- a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs @@ -6,14 +6,11 @@ namespace Open.Collections.Synchronized; -/// -public class ReadWriteSynchronizedDictionaryWrapper - : ReadWriteSynchronizedCollectionWrapper, TDictionary>, IDictionary +public class ReadWriteSynchronizedDictionaryWrapper( + TDictionary dictionary, bool owner = false) + : ReadWriteSynchronizedCollectionWrapper, TDictionary>(dictionary, owner), IDictionary where TDictionary : class, IDictionary { - /// - public ReadWriteSynchronizedDictionaryWrapper(TDictionary dictionary, bool owner = false) : base(dictionary, owner) { } - /// [ExcludeFromCodeCoverage] public virtual TValue this[TKey key] @@ -79,7 +76,11 @@ public virtual bool Remove(TKey key) /// [ExcludeFromCodeCoverage] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(TKey key, out TValue value) + public bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#endif + out TValue value) => InternalSource.TryGetValue(key, out value); /// @@ -104,17 +105,16 @@ public virtual bool IfNotContainsKey(TKey key, Action> } [ExcludeFromCodeCoverage] -public class ReadWriteSynchronizedDictionaryWrapper - : ReadWriteSynchronizedDictionaryWrapper> +public class ReadWriteSynchronizedDictionaryWrapper( + IDictionary dictionary, bool owner = false) + : ReadWriteSynchronizedDictionaryWrapper>(dictionary, owner) { - public ReadWriteSynchronizedDictionaryWrapper(IDictionary dictionary, bool owner = false) : base(dictionary, owner) - { - } } [ExcludeFromCodeCoverage] public class ReadWriteSynchronizedDictionary : ReadWriteSynchronizedDictionaryWrapper + where TKey : notnull { public ReadWriteSynchronizedDictionary() : base(new Dictionary()) { } diff --git a/source/Synchronized/ReadWriteSynchronizedHashSet.cs b/source/Synchronized/ReadWriteSynchronizedHashSet.cs index 8aa4f55..6c60127 100644 --- a/source/Synchronized/ReadWriteSynchronizedHashSet.cs +++ b/source/Synchronized/ReadWriteSynchronizedHashSet.cs @@ -6,15 +6,28 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized that uses a for thread safety. +/// public sealed class ReadWriteSynchronizedHashSet : ReadWriteSynchronizedCollectionWrapper>, ISet { + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] - public ReadWriteSynchronizedHashSet() : base(new HashSet()) { } + public ReadWriteSynchronizedHashSet() : base([]) { } + /// + /// Constructs a new instance with the specified capacity. + /// + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedHashSet(IEnumerable collection) : base(new HashSet(collection)) { } + /// + /// Constructs a new instance with the specified capacity and comparer. + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedHashSet(IEnumerable collection, IEqualityComparer comparer) : base(new HashSet(collection, comparer)) { } diff --git a/source/Synchronized/ReadWriteSynchronizedLinkedList.cs b/source/Synchronized/ReadWriteSynchronizedLinkedList.cs index 3377598..12c590b 100644 --- a/source/Synchronized/ReadWriteSynchronizedLinkedList.cs +++ b/source/Synchronized/ReadWriteSynchronizedLinkedList.cs @@ -4,25 +4,36 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized wrapper for that uses a for thread safety. +/// +/// public sealed class ReadWriteSynchronizedLinkedList : ReadWriteSynchronizedCollectionWrapper>, ILinkedList { + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedLinkedList() : base(new LinkedList()) { } + /// + /// Constructs a new instance with the specified collection. + /// + /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedLinkedList(IEnumerable collection) : base(new LinkedList(collection)) { } /// [ExcludeFromCodeCoverage] - public LinkedListNode First + public LinkedListNode? First => InternalSource.First; /// [ExcludeFromCodeCoverage] - public LinkedListNode Last + public LinkedListNode? Last => InternalSource.Last; /// diff --git a/source/Synchronized/ReadWriteSynchronizedList.cs b/source/Synchronized/ReadWriteSynchronizedList.cs index e7a832e..0c70fc6 100644 --- a/source/Synchronized/ReadWriteSynchronizedList.cs +++ b/source/Synchronized/ReadWriteSynchronizedList.cs @@ -3,16 +3,28 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized that uses a for thread safety. +/// [ExcludeFromCodeCoverage] public sealed class ReadWriteSynchronizedList : ReadWriteSynchronizedListWrapper { + /// + /// Constructs a new instance. + /// public ReadWriteSynchronizedList() - : base(new List()) { } + : base([]) { } + /// + /// Constructs a new instance with the specified capacity. + /// public ReadWriteSynchronizedList(int capacity = 0) : base(new List(capacity)) { } + /// + /// Constructs a new instance with the specified collection. + /// public ReadWriteSynchronizedList(IEnumerable collection) : base(new List(collection)) { } } diff --git a/source/Synchronized/ReadWriteSynchronizedListWrapper.cs b/source/Synchronized/ReadWriteSynchronizedListWrapper.cs index 6b649cf..2ac52ea 100644 --- a/source/Synchronized/ReadWriteSynchronizedListWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedListWrapper.cs @@ -4,13 +4,14 @@ namespace Open.Collections.Synchronized; -public class ReadWriteSynchronizedListWrapper - : ReadWriteSynchronizedCollectionWrapper, IList +/// +/// A synchronized wrapper for a list that uses a for synchronization. +/// +public class ReadWriteSynchronizedListWrapper( + TList list, bool owner = false) + : ReadWriteSynchronizedCollectionWrapper(list, owner), IList where TList : class, IList { - [ExcludeFromCodeCoverage] - public ReadWriteSynchronizedListWrapper(TList list, bool owner = false) : base(list, owner) { } - // This is a simplified version. // It could be possible to allow indexed values to change independently of one another. // If that fine grained of read-write control is necessary, then use the ThreadSafety utility and extensions. @@ -58,12 +59,12 @@ public override bool Remove(T item) } } +/// +/// A synchronized wrapper for a list that uses a for synchronization. +/// [ExcludeFromCodeCoverage] -public class ReadWriteSynchronizedListWrapper - : ReadWriteSynchronizedListWrapper> +public class ReadWriteSynchronizedListWrapper( + IList list, bool owner = false) + : ReadWriteSynchronizedListWrapper>(list, owner) { - public ReadWriteSynchronizedListWrapper(IList list, bool owner = false) - : base(list, owner) - { - } } \ No newline at end of file diff --git a/source/Synchronized/TrackedDictionaryWrapper.cs b/source/Synchronized/TrackedDictionaryWrapper.cs index 44efb4d..c3df836 100644 --- a/source/Synchronized/TrackedDictionaryWrapper.cs +++ b/source/Synchronized/TrackedDictionaryWrapper.cs @@ -5,35 +5,52 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized dictionary that can be tracked for changes. +/// public class TrackedDictionaryWrapper : TrackedCollectionWrapper, TDictionary>, IDictionary + where TKey : notnull where TDictionary : class, IDictionary { + /// + /// Construct a new instance with the provide dictionary and optional synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(TDictionary dictionary, ModificationSynchronizer? sync = null) : base(dictionary, sync) { } + /// + /// Construct a new instance with the provide dictionary and a new synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(TDictionary dictionary, out ModificationSynchronizer sync) : base(dictionary, out sync) { } + /// [ExcludeFromCodeCoverage] public ICollection Keys => InternalSource.Keys; + /// [ExcludeFromCodeCoverage] public ICollection Values => InternalSource.Values; /// [ExcludeFromCodeCoverage] - public bool TryGetValue(TKey key, out TValue value) + public bool TryGetValue(TKey key, +#if NET9_0_OR_GREATER + [MaybeNullWhen(false)] +#else +#endif + out TValue value) { - TValue v = default!; + TValue? v = default; bool result = Sync!.Reading(() => InternalSource.TryGetValue(key, out v)); - value = v; + value = v!; return result; } @@ -45,6 +62,7 @@ public TValue this[TKey key] set => SetValue(key, value); } + /// public bool SetValue(TKey key, TValue value) => Sync!.Modifying( AssertIsAliveDelegate, @@ -71,6 +89,7 @@ public bool ContainsKey(TKey key) => Sync!.Reading( () => AssertIsAlive() && InternalSource.ContainsKey(key)); + /// protected virtual int AddSynchronized(TKey key, TValue value) { Sync!.Modifying( @@ -96,7 +115,7 @@ public void Add(TKey key, TValue value) /// public bool Remove(TKey key) { - TValue value = default!; + TValue? value = default; return Sync!.Modifying( () => AssertIsAlive() && InternalSource.TryGetValue(key, out value), @@ -109,20 +128,24 @@ public bool Remove(TKey key) version => { if (HasChangedListeners) // Avoid creating KVP unnecessarily. - OnChanged(ItemChange.Removed, KeyValuePair.Create(key, value), version); + OnChanged(ItemChange.Removed, KeyValuePair.Create(key, value!), version); }); } } +/// public class TrackedDictionaryWrapper : TrackedDictionaryWrapper> + where TKey : notnull { + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(IDictionary dictionary, ModificationSynchronizer? sync = null) : base(dictionary, sync) { } + /// [ExcludeFromCodeCoverage] public TrackedDictionaryWrapper(IDictionary dictionary, out ModificationSynchronizer sync) : base(dictionary, out sync) @@ -130,33 +153,50 @@ public TrackedDictionaryWrapper(IDictionary dictionary, out Modifi } } +/// public class TrackedDictionary : TrackedDictionaryWrapper + where TKey : notnull { + /// + /// Constructs a new instance with the specified capacity and optional synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(int capacity, ModificationSynchronizer? sync = null) : base(new Dictionary(capacity), sync) { } + /// + /// Constructs a new instance with the specified capacity and a new synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(int capacity, out ModificationSynchronizer sync) : base(new Dictionary(capacity), out sync) { } + /// + /// Constructs a new instance with an optional synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(ModificationSynchronizer? sync = null) : base(new Dictionary(), sync) { } + /// + /// Constructs a new instance with a new synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedDictionary(out ModificationSynchronizer sync) : base(new Dictionary(), out sync) { } + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] public TrackedDictionary() : this(null) { } } diff --git a/source/Synchronized/TrackedIndexedDictionaryWrapper.cs b/source/Synchronized/TrackedIndexedDictionaryWrapper.cs index e5b31d4..59bfa5d 100644 --- a/source/Synchronized/TrackedIndexedDictionaryWrapper.cs +++ b/source/Synchronized/TrackedIndexedDictionaryWrapper.cs @@ -7,6 +7,7 @@ namespace Open.Collections.Synchronized; /// public class TrackedIndexedDictionaryWrapper : TrackedDictionaryWrapper, IIndexedDictionary + where TKey : notnull where TDictionary : class, IIndexedDictionary { /// @@ -135,6 +136,7 @@ protected override int AddSynchronized(TKey key, TValue value) public class TrackedIndexedDictionaryWrapper : TrackedIndexedDictionaryWrapper> + where TKey : notnull { /// [ExcludeFromCodeCoverage] @@ -153,6 +155,7 @@ public TrackedIndexedDictionaryWrapper(IIndexedDictionary dictiona public sealed class TrackedIndexedDictionary : TrackedIndexedDictionaryWrapper + where TKey : notnull { [ExcludeFromCodeCoverage] public TrackedIndexedDictionary(int capacity, ModificationSynchronizer? sync = null) diff --git a/source/Synchronized/TrackedListWrapper.cs b/source/Synchronized/TrackedListWrapper.cs index a9be517..eb84b5f 100644 --- a/source/Synchronized/TrackedListWrapper.cs +++ b/source/Synchronized/TrackedListWrapper.cs @@ -5,13 +5,22 @@ namespace Open.Collections.Synchronized; +/// +/// A synchronized list that tracks changes. +/// public class TrackedListWrapper : TrackedCollectionWrapper>, IList { + /// + /// Constructs a new instance with the specified list and optional modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedListWrapper(IList list, ModificationSynchronizer? sync = null) : base(list, sync) { } + /// + /// Constructs a new instance with the specified list and a new modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedListWrapper(IList list, out ModificationSynchronizer sync) : base(list, out sync) { @@ -24,6 +33,7 @@ public T this[int index] set => SetValue(index, value); } + /// public bool SetValue(int index, T value) => Sync!.Modifying( AssertIsAliveDelegate, @@ -115,32 +125,50 @@ public bool Replace(T target, T replacement, bool throwIfNotFound = false) } } +/// +/// A synchronized list that tracks changes. +/// public sealed class TrackedList : TrackedListWrapper { + /// + /// Constructs a new instance with the specified initial capacity and optional modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(int capacity, ModificationSynchronizer? sync = null) : base(new List(capacity), sync) { } + /// + /// Constructs a new instance with the specified initial capacity and a new modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(int capacity, out ModificationSynchronizer sync) : base(new List(capacity), out sync) { } + /// + /// Constructs a new instance using the provided modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(ModificationSynchronizer? sync) - : base(new List(), sync) + : base([], sync) { } + /// + /// Constructs a new instance with a new modification synchronizer. + /// [ExcludeFromCodeCoverage] public TrackedList(out ModificationSynchronizer sync) - : base(new List(), out sync) + : base([], out sync) { } + /// + /// Constructs a new instance. + /// [ExcludeFromCodeCoverage] public TrackedList() : this(null) { } } \ No newline at end of file diff --git a/testing/Open.Collections.Tests/BasicCollectionTests.cs b/testing/Open.Collections.Tests/BasicCollectionTests.cs index 2c6a99b..a7d32fd 100644 --- a/testing/Open.Collections.Tests/BasicCollectionTests.cs +++ b/testing/Open.Collections.Tests/BasicCollectionTests.cs @@ -7,18 +7,14 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicCollectionTests +public abstract class BasicCollectionTests(TCollection collection) where TCollection : ICollection, new() { - protected BasicCollectionTests(TCollection collection) - => Collection = collection; - protected BasicCollectionTests() : this(new()) { } - protected readonly TCollection Collection; + protected readonly TCollection Collection = collection; - [Fact] - public virtual TCollection AssertWhenDisposed() + protected virtual TCollection AssertWhenDisposedCore() { // Policy: // Ideally an exception should throw whenever access occurs after disposal. @@ -40,6 +36,10 @@ public virtual TCollection AssertWhenDisposed() return c; } + [Fact] + public void AssertWhenDisposed() + => AssertWhenDisposedCore(); + protected static void ThrowsDisposed(Action action) => Assert.Throws(action); diff --git a/testing/Open.Collections.Tests/BasicDictionaryTests.cs b/testing/Open.Collections.Tests/BasicDictionaryTests.cs index 8257f28..ba7c5a8 100644 --- a/testing/Open.Collections.Tests/BasicDictionaryTests.cs +++ b/testing/Open.Collections.Tests/BasicDictionaryTests.cs @@ -5,18 +5,14 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicDictionaryTests +public abstract class BasicDictionaryTests(TDictionary dictionary) where TDictionary : IDictionary, new() { - protected BasicDictionaryTests(TDictionary dictionary) - => Dictionary = dictionary; - protected BasicDictionaryTests() : this(new()) { } - protected readonly TDictionary Dictionary; + protected readonly TDictionary Dictionary = dictionary; - [Fact] - public TDictionary AssertWhenDisposed() + protected TDictionary AssertWhenDisposedCore() { TDictionary d = new(); d.Add(5, 10); @@ -33,6 +29,10 @@ public TDictionary AssertWhenDisposed() return d; } + [Fact] + public void AssertWhenDisposed() + => AssertWhenDisposedCore(); + static void ThrowsDisposed(Action action) => Assert.Throws(action); diff --git a/testing/Open.Collections.Tests/BasicLinkedListTests.cs b/testing/Open.Collections.Tests/BasicLinkedListTests.cs index f7fd98c..92ef919 100644 --- a/testing/Open.Collections.Tests/BasicLinkedListTests.cs +++ b/testing/Open.Collections.Tests/BasicLinkedListTests.cs @@ -3,12 +3,9 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicLinkedListTests : BasicCollectionTests +public abstract class BasicLinkedListTests(TList collection) : BasicCollectionTests(collection) where TList : ILinkedList, new() { - protected BasicLinkedListTests(TList collection) - : base(collection) { } - protected BasicLinkedListTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/BasicListTests.cs b/testing/Open.Collections.Tests/BasicListTests.cs index d80e120..4b762f2 100644 --- a/testing/Open.Collections.Tests/BasicListTests.cs +++ b/testing/Open.Collections.Tests/BasicListTests.cs @@ -4,17 +4,15 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class BasicListTests - : BasicCollectionTests +public abstract class BasicListTests(TList list) + : BasicCollectionTests(list) where TList : IList, new() { - protected BasicListTests(TList list) : base(list) { } - protected BasicListTests() : this(new()) { } - public override TList AssertWhenDisposed() + protected override TList AssertWhenDisposedCore() { - var list = base.AssertWhenDisposed(); + var list = base.AssertWhenDisposedCore(); if (list is not IDisposable) return list; ThrowsDisposed(() => list.IndexOf(5)); return list; diff --git a/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs b/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs index 5ce5ff9..b8ced0c 100644 --- a/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs @@ -1,9 +1,10 @@ using Open.Collections.Synchronized; namespace Open.Collections.Tests; + public class ConcurrentListTests : BasicListTests> { - public ConcurrentListTests() : base(new()) + public ConcurrentListTests() : base([]) { } } diff --git a/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs index 9400be5..ea5a262 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs @@ -1,12 +1,9 @@ using Open.Collections.Synchronized; namespace Open.Collections.Tests; + public class LockSyncDictionaryTests - : ParallelDictionaryTests> -{ -} + : ParallelDictionaryTests>; public class LockSyncIndexedDictionaryTests - : ParallelDictionaryTests> -{ -} \ No newline at end of file + : ParallelDictionaryTests>; \ No newline at end of file diff --git a/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs index 3251a1f..1324411 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs @@ -1,10 +1,11 @@ using Open.Collections.Synchronized; namespace Open.Collections.Tests; + public class LockSyncLinkedListTests : BasicLinkedListTests> { - public LockSyncLinkedListTests() : base(new()) + public LockSyncLinkedListTests() : base([]) { } } diff --git a/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs index eb60f4b..517525e 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs @@ -2,6 +2,4 @@ namespace Open.Collections.Tests; public class LockSyncListTests - : ParallelListTests> -{ -} + : ParallelListTests>; diff --git a/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs index 42e3bb3..f9be9a9 100644 --- a/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs @@ -3,9 +3,7 @@ namespace Open.Collections.Tests; -public class OrderedDictionaryTests : OrderedDictionaryTests> -{ -} +public class OrderedDictionaryTests : OrderedDictionaryTests>; public class IndexedDictionaryTests : OrderedDictionaryTests> { diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs index 58a1ce3..79f40ff 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs @@ -2,11 +2,7 @@ namespace Open.Collections.Tests; public class ReadWriteSyncDictionaryTests - : ParallelDictionaryTests> -{ -} + : ParallelDictionaryTests>; public class ReadWriteSyncIndexedDictionaryTests - : ParallelDictionaryTests> -{ -} \ No newline at end of file + : ParallelDictionaryTests>; \ No newline at end of file diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs index 26e0482..1daa2a1 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs @@ -4,7 +4,7 @@ namespace Open.Collections.Tests; public class ReadWriteSyncLinkedListTests : BasicLinkedListTests> { - public ReadWriteSyncLinkedListTests() : base(new()) + public ReadWriteSyncLinkedListTests() : base([]) { } } diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs index 010de79..d6b5a90 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs @@ -2,6 +2,4 @@ namespace Open.Collections.Tests; public class ReadWriteSyncListTests - : ParallelListTests> -{ -} + : ParallelListTests>; diff --git a/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs index 2c0f137..857c4fb 100644 --- a/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs @@ -2,11 +2,7 @@ namespace Open.Collections.Tests; public class TrackedDictionaryTests - : ParallelDictionaryTests> -{ -} + : ParallelDictionaryTests>; public class TrackedIndexedDictionaryTests - : ParallelDictionaryTests> -{ -} \ No newline at end of file + : ParallelDictionaryTests>; \ No newline at end of file diff --git a/testing/Open.Collections.Tests/CombinationTests.cs b/testing/Open.Collections.Tests/CombinationTests.cs index 64ee22e..fea5d6c 100644 --- a/testing/Open.Collections.Tests/CombinationTests.cs +++ b/testing/Open.Collections.Tests/CombinationTests.cs @@ -6,19 +6,19 @@ namespace Open.Collections.Tests; public class CombinationTests { - static readonly ImmutableArray Set1 = ImmutableArray.Create(1, 2); - static readonly ImmutableArray Set2 = ImmutableArray.Create('A', 'C', 'E'); - static readonly ImmutableArray Set3 = ImmutableArray.Create(0, 1); + static readonly ImmutableArray Set1 = [1, 2]; + static readonly ImmutableArray Set2 = ['A', 'C', 'E']; + static readonly ImmutableArray Set3 = [0, 1]; [Fact] public void TestCombination1() { - int[][] expected = new int[][] { - new int[] { 1, 1 }, - new int[] { 1, 2 }, - new int[] { 2, 1 }, - new int[] { 2, 2 }, - }; + int[][] expected = [ + [1, 1], + [1, 2], + [2, 1], + [2, 2], + ]; int[][] actual = Set1.Combinations().ToArray(); Assert.Equal(expected, actual); } @@ -26,11 +26,11 @@ public void TestCombination1() [Fact] public void TestCombination1Distinct() { - int[][] expected = new int[][] { - new int[] { 1, 1 }, - new int[] { 1, 2 }, - new int[] { 2, 2 }, - }; + int[][] expected = [ + [1, 1], + [1, 2], + [2, 2], + ]; int[][] actual = Set1.CombinationsDistinct().ToArray(); Assert.Equal(expected, actual); } @@ -38,17 +38,17 @@ public void TestCombination1Distinct() [Fact] public void TestCombination2() { - char[][] expected = new char[][] { - new char[] { 'A', 'A' }, - new char[] { 'A', 'C' }, - new char[] { 'A', 'E' }, - new char[] { 'C', 'A' }, - new char[] { 'C', 'C' }, - new char[] { 'C', 'E' }, - new char[] { 'E', 'A' }, - new char[] { 'E', 'C' }, - new char[] { 'E', 'E' }, - }; + char[][] expected = [ + ['A', 'A'], + ['A', 'C'], + ['A', 'E'], + ['C', 'A'], + ['C', 'C'], + ['C', 'E'], + ['E', 'A'], + ['E', 'C'], + ['E', 'E'], + ]; char[][] actual = Set2.Combinations(2).ToArray(); Assert.Equal(expected, actual); } @@ -56,14 +56,14 @@ public void TestCombination2() [Fact] public void TestCombination2Distinct() { - char[][] expected = new char[][] { - new char[] { 'A', 'A' }, - new char[] { 'A', 'C' }, - new char[] { 'A', 'E' }, - new char[] { 'C', 'C' }, - new char[] { 'C', 'E' }, - new char[] { 'E', 'E' }, - }; + char[][] expected = [ + ['A', 'A'], + ['A', 'C'], + ['A', 'E'], + ['C', 'C'], + ['C', 'E'], + ['E', 'E'], + ]; char[][] actual = Set2.CombinationsDistinct(2).ToArray(); Assert.Equal(expected, actual); } @@ -71,24 +71,24 @@ public void TestCombination2Distinct() [Fact] public void TestCombination3() { - int[][] expected = new int[][] { - new int[] { 0, 0, 0, 0 }, - new int[] { 0, 0, 0, 1 }, - new int[] { 0, 0, 1, 0 }, - new int[] { 0, 0, 1, 1 }, - new int[] { 0, 1, 0, 0 }, - new int[] { 0, 1, 0, 1 }, - new int[] { 0, 1, 1, 0 }, - new int[] { 0, 1, 1, 1 }, - new int[] { 1, 0, 0, 0 }, - new int[] { 1, 0, 0, 1 }, - new int[] { 1, 0, 1, 0 }, - new int[] { 1, 0, 1, 1 }, - new int[] { 1, 1, 0, 0 }, - new int[] { 1, 1, 0, 1 }, - new int[] { 1, 1, 1, 0 }, - new int[] { 1, 1, 1, 1 }, - }; + int[][] expected = [ + [0, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 0, 1, 1], + [0, 1, 0, 0], + [0, 1, 0, 1], + [0, 1, 1, 0], + [0, 1, 1, 1], + [1, 0, 0, 0], + [1, 0, 0, 1], + [1, 0, 1, 0], + [1, 0, 1, 1], + [1, 1, 0, 0], + [1, 1, 0, 1], + [1, 1, 1, 0], + [1, 1, 1, 1], + ]; int[][] actual = Set3.Combinations(4).ToArray(); Assert.Equal(expected, actual); } diff --git a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj index 0b54a3a..bb06d99 100644 --- a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj +++ b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj @@ -1,9 +1,10 @@  - net6.0 + net9.0 false + IDE0305;IDE0301;IDE0310; @@ -11,18 +12,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/testing/Open.Collections.Tests/OrderedDictionaryTests.cs b/testing/Open.Collections.Tests/OrderedDictionaryTests.cs index d5a6915..cfc4bb3 100644 --- a/testing/Open.Collections.Tests/OrderedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/OrderedDictionaryTests.cs @@ -4,12 +4,9 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class OrderedDictionaryTests : BasicDictionaryTests +public abstract class OrderedDictionaryTests(TDictionary dictionary) : BasicDictionaryTests(dictionary) where TDictionary : IDictionary, new() { - protected OrderedDictionaryTests(TDictionary dictionary) - : base(dictionary) { } - protected OrderedDictionaryTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/ParallelDictionaryTests.cs b/testing/Open.Collections.Tests/ParallelDictionaryTests.cs index fb438bb..986ccf3 100644 --- a/testing/Open.Collections.Tests/ParallelDictionaryTests.cs +++ b/testing/Open.Collections.Tests/ParallelDictionaryTests.cs @@ -4,13 +4,10 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class ParallelDictionaryTests - : BasicDictionaryTests +public abstract class ParallelDictionaryTests(TDictionary dictionary) + : BasicDictionaryTests(dictionary) where TDictionary : IDictionary, new() { - protected ParallelDictionaryTests(TDictionary dictionary) - : base(dictionary) { } - protected ParallelDictionaryTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/ParallelListTests.cs b/testing/Open.Collections.Tests/ParallelListTests.cs index db9d8a3..81955af 100644 --- a/testing/Open.Collections.Tests/ParallelListTests.cs +++ b/testing/Open.Collections.Tests/ParallelListTests.cs @@ -4,13 +4,11 @@ using Xunit; namespace Open.Collections.Tests; -public abstract class ParallelListTests - : BasicListTests +public abstract class ParallelListTests( + TList list) + : BasicListTests(list) where TList : IList, new() { - protected ParallelListTests(TList list) - : base(list) { } - protected ParallelListTests() : this(new()) { } diff --git a/testing/Open.Collections.Tests/PermutationTests.cs b/testing/Open.Collections.Tests/PermutationTests.cs index a301aea..1b5d684 100644 --- a/testing/Open.Collections.Tests/PermutationTests.cs +++ b/testing/Open.Collections.Tests/PermutationTests.cs @@ -8,16 +8,16 @@ namespace Open.Collections.Tests; public class PermutationTests { - static readonly ImmutableArray Set1 = ImmutableArray.Create(1, 2); + static readonly ImmutableArray Set1 = [1, 2]; static readonly ReadOnlyMemory Set2 = new char[] { 'A', 'B', 'C' }; [Fact] public void TestPermutation1a() { - int[][] expected = new int[][] { - new int[] { 1, 2 }, - new int[] { 2, 1 }, - }; + int[][] expected = [ + [1, 2], + [2, 1], + ]; int[][] actual = Set1.Permutations().ToArray(); actual.Length.Should().Be(2); Assert.Equal(expected, actual); @@ -33,10 +33,10 @@ public void TestPermutation1a() [InlineData(24)] public void TestPermutation1b(int bufferLength) { - int[][] expected = new int[][] { - new int[] { 1, 2 }, - new int[] { 2, 1 }, - }; + int[][] expected = [ + [1, 2], + [2, 1], + ]; int[] buffer = new int[bufferLength]; @@ -48,14 +48,14 @@ public void TestPermutation1b(int bufferLength) [Fact] public void TestPermutation2() { - char[][] expected = new char[][] { - new char[] { 'A', 'B', 'C' }, - new char[] { 'B', 'A', 'C' }, - new char[] { 'C', 'A', 'B' }, - new char[] { 'A', 'C', 'B' }, - new char[] { 'B', 'C', 'A' }, - new char[] { 'C', 'B', 'A' }, - }; + char[][] expected = [ + ['A', 'B', 'C'], + ['B', 'A', 'C'], + ['C', 'A', 'B'], + ['A', 'C', 'B'], + ['B', 'C', 'A'], + ['C', 'B', 'A'], + ]; char[][] actual = Set2.Permutations().ToArray(); Assert.Equal(expected, actual); } diff --git a/testing/Open.Collections.Tests/PermutorTests.cs b/testing/Open.Collections.Tests/PermutorTests.cs index f9f3a16..a183465 100644 --- a/testing/Open.Collections.Tests/PermutorTests.cs +++ b/testing/Open.Collections.Tests/PermutorTests.cs @@ -12,7 +12,7 @@ public class PermutorTests [Fact] public void TestNoDuplicatePermutations() { - int[] numbers = { 1, 2, 3, 4 }; + int[] numbers = [1, 2, 3, 4]; var permutations = numbers.Permutations().Select(p => string.Join(",", p.Span.ToArray())).ToHashSet(); int expectedCount = Factorial(numbers.Length); @@ -22,17 +22,17 @@ public void TestNoDuplicatePermutations() [Fact] public void TestSpecificPermutation() { - int[] numbers = { 1, 2, 3 }; + int[] numbers = [1, 2, 3]; var permutations = numbers.AsMemory().Permutations().Select(m=>m.ToArray()).ToList(); permutations.Count.Should().Be(6); - int[] expectedPermutation = new[] { 2, 1, 3 }; + int[] expectedPermutation = [2, 1, 3]; Assert.Contains(expectedPermutation, permutations); } [Fact] public void TestExactPermutationsFor123() { - int[] numbers = { 1, 2, 3 }; + int[] numbers = [1, 2, 3]; var expectedPermutations = new List { "1,2,3", @@ -57,7 +57,7 @@ public void TestExactPermutationsFor123() [Fact] public void TestStableLexicographicOrder() { - int[] original = { 1, 2, 3 }; + int[] original = [1, 2, 3]; Span span = original.AsSpan(); var permutations = new List(); @@ -80,11 +80,10 @@ public void TestStableLexicographicOrder() Assert.Equal(expectedPermutations, permutations); } - [Fact] public void TestStableHeapsAlgorithmOrder() { - int[] original = { 1, 2, 3 }; + int[] original = [1, 2, 3]; var permutations = new List(); foreach(var s in original.Permutations()) permutations.Add(string.Join(",", s.ToArray())); @@ -105,7 +104,7 @@ public void TestStableHeapsAlgorithmOrder() [Fact] public void TestStableIndexedOrder() { - int[] original = { 1, 2, 3 }; + int[] original = [1, 2, 3]; var permutations = new List(); for (int i = 0; i < 6; ++i) { @@ -129,7 +128,6 @@ public void TestStableIndexedOrder() Assert.Equal(expectedPermutations, permutations); } - private static int Factorial(int n) { int result = 1; diff --git a/testing/Open.Collections.Tests/SubsetTests.cs b/testing/Open.Collections.Tests/SubsetTests.cs index 2681c17..a276346 100644 --- a/testing/Open.Collections.Tests/SubsetTests.cs +++ b/testing/Open.Collections.Tests/SubsetTests.cs @@ -8,20 +8,20 @@ namespace Open.Collections.Tests; public class SubsetTests { - static readonly ImmutableArray Set1 = ImmutableArray.Create(1, 2, 3); - static readonly ImmutableArray Set2 = ImmutableArray.Create('A', 'C', 'E'); - static readonly ImmutableArray Set3 = ImmutableArray.Create('A', 'B', 'C', 'D'); + static readonly ImmutableArray Set1 = [1, 2, 3]; + static readonly ImmutableArray Set2 = ['A', 'C', 'E']; + static readonly ImmutableArray Set3 = ['A', 'B', 'C', 'D']; static readonly ImmutableArray Set4 = Enumerable.Range(1, 5).ToImmutableArray(); static readonly ImmutableArray Set5 = Enumerable.Range(1, 11).ToImmutableArray(); [Fact] public void TestSubset1_2() { - int[][] expected = new int[][] { - new int[] { 1, 2 }, - new int[] { 1, 3 }, - new int[] { 2, 3 }, - }; + int[][] expected = [ + [1, 2], + [1, 3], + [2, 3], + ]; int[][] actual = Set1.Subsets(2).ToArray(); Assert.Equal(expected, actual); @@ -35,11 +35,11 @@ public void TestSubset1_2() [Fact] public void TestSubset2_2() { - char[][] expected = new char[][] { - new char[] { 'A', 'C' }, - new char[] { 'A', 'E' }, - new char[] { 'C', 'E' }, - }; + char[][] expected = [ + ['A', 'C'], + ['A', 'E'], + ['C', 'E'], + ]; char[][] actual = Set2.Subsets(2).ToArray(); Assert.Equal(expected, actual); @@ -50,14 +50,14 @@ public void TestSubset2_2() [Fact] public void TestSubset3_2() { - char[][] expected = new char[][] { - new char[] { 'A', 'B' }, - new char[] { 'A', 'C' }, - new char[] { 'B', 'C' }, - new char[] { 'A', 'D' }, - new char[] { 'B', 'D' }, - new char[] { 'C', 'D' }, - }; + char[][] expected = [ + ['A', 'B'], + ['A', 'C'], + ['B', 'C'], + ['A', 'D'], + ['B', 'D'], + ['C', 'D'], + ]; Assert.Equal(expected.Length, Set3.Subsets(2).Count()); char[][] progressive = Set3.SubsetsProgressive(2).ToArray(); @@ -67,12 +67,12 @@ public void TestSubset3_2() [Fact] public void TestSubset3_3() { - char[][] expected = new char[][] { - new char[] { 'A', 'B', 'C' }, - new char[] { 'A', 'B', 'D' }, - new char[] { 'A', 'C', 'D' }, - new char[] { 'B', 'C', 'D' }, - }; + char[][] expected = [ + ['A', 'B', 'C'], + ['A', 'B', 'D'], + ['A', 'C', 'D'], + ['B', 'C', 'D'], + ]; char[][] actual = Set3.Subsets(3).ToArray(); Assert.Equal(expected, actual); @@ -100,13 +100,13 @@ static T[] Selector(ArrayPoolSegment e) [Fact] public void TestSubset4_4() { - int[][] expected = new int[][] { - new int[] { 1, 2, 3, 4 }, - new int[] { 1, 2, 3, 5 }, - new int[] { 1, 2, 4, 5 }, - new int[] { 1, 3, 4, 5 }, - new int[] { 2, 3, 4, 5 }, - }; + int[][] expected = [ + [1, 2, 3, 4], + [1, 2, 3, 5], + [1, 2, 4, 5], + [1, 3, 4, 5], + [2, 3, 4, 5], + ]; int[][] actual = Set4.Subsets(4).ToArray(); Assert.Equal(expected, actual); @@ -127,18 +127,18 @@ public void TestSubset4_4() [Fact] public void TestSubset4_3() { - int[][] expected = new int[][] { - new int[] { 1, 2, 3 }, - new int[] { 1, 2, 4 }, - new int[] { 1, 3, 4 }, - new int[] { 2, 3, 4 }, - new int[] { 1, 2, 5 }, - new int[] { 1, 3, 5 }, - new int[] { 1, 4, 5 }, - new int[] { 2, 3, 5 }, - new int[] { 2, 4, 5 }, - new int[] { 3, 4, 5 }, - }; + int[][] expected = [ + [1, 2, 3], + [1, 2, 4], + [1, 3, 4], + [2, 3, 4], + [1, 2, 5], + [1, 3, 5], + [1, 4, 5], + [2, 3, 5], + [2, 4, 5], + [3, 4, 5], + ]; int[][] actual = Set4.SubsetsProgressive(3).ToArray(); Assert.Equal(expected, actual); diff --git a/testing/Open.Collections.Tests/TrieTests.cs b/testing/Open.Collections.Tests/TrieTests.cs index fd9f5fd..ad2da83 100644 --- a/testing/Open.Collections.Tests/TrieTests.cs +++ b/testing/Open.Collections.Tests/TrieTests.cs @@ -4,15 +4,15 @@ namespace Open.Collections.Tests; public static class TrieTests { - static readonly string[] Examples = new[] - { + static readonly string[] Examples = + [ "", "abcd", "dcba", "abcdef", "the brown fox", "xxx" - }; + ]; [Fact] public static void TrieValidate() From df9c947e638550d1007c6e88afe8ca3f1ac4ddf9 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Sat, 16 Nov 2024 08:01:18 -0800 Subject: [PATCH 05/18] Corrected IDE0310 --- .../Open.Collections.Tests/Collections/ConcurrentListTests.cs | 2 +- .../Collections/LockSyncDictionaryTests.cs | 2 +- .../Collections/LockSyncLinkedListTests.cs | 2 +- testing/Open.Collections.Tests/Collections/LockSyncListTests.cs | 2 +- .../Collections/OrderedDictionaryTests.cs | 2 +- .../Collections/ReadWriteSyncLDictionaryTests.cs | 2 +- .../Collections/ReadWriteSyncLinkedListTests.cs | 2 +- .../Collections/ReadWriteSyncListTests.cs | 2 +- .../Collections/TrackedDictionaryTests.cs | 2 +- testing/Open.Collections.Tests/Open.Collections.Tests.csproj | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs b/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs index b8ced0c..6e21ba4 100644 --- a/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ConcurrentListTests.cs @@ -1,6 +1,6 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class ConcurrentListTests : BasicListTests> { diff --git a/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs index ea5a262..5777b03 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncDictionaryTests.cs @@ -1,6 +1,6 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class LockSyncDictionaryTests : ParallelDictionaryTests>; diff --git a/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs index 1324411..ae56c87 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncLinkedListTests.cs @@ -1,6 +1,6 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class LockSyncLinkedListTests : BasicLinkedListTests> diff --git a/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs b/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs index 517525e..6b8e1f2 100644 --- a/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs +++ b/testing/Open.Collections.Tests/Collections/LockSyncListTests.cs @@ -1,5 +1,5 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class LockSyncListTests : ParallelListTests>; diff --git a/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs index f9be9a9..ce0138e 100644 --- a/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/OrderedDictionaryTests.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Xunit; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class OrderedDictionaryTests : OrderedDictionaryTests>; diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs index 79f40ff..cceabf9 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLDictionaryTests.cs @@ -1,6 +1,6 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class ReadWriteSyncDictionaryTests : ParallelDictionaryTests>; diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs index 1daa2a1..15f85f8 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncLinkedListTests.cs @@ -1,6 +1,6 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class ReadWriteSyncLinkedListTests : BasicLinkedListTests> { diff --git a/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs b/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs index d6b5a90..14c185f 100644 --- a/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs +++ b/testing/Open.Collections.Tests/Collections/ReadWriteSyncListTests.cs @@ -1,5 +1,5 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class ReadWriteSyncListTests : ParallelListTests>; diff --git a/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs b/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs index 857c4fb..2699fb9 100644 --- a/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs +++ b/testing/Open.Collections.Tests/Collections/TrackedDictionaryTests.cs @@ -1,6 +1,6 @@ using Open.Collections.Synchronized; -namespace Open.Collections.Tests; +namespace Open.Collections.Tests.Collections; public class TrackedDictionaryTests : ParallelDictionaryTests>; diff --git a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj index bb06d99..7e8c728 100644 --- a/testing/Open.Collections.Tests/Open.Collections.Tests.csproj +++ b/testing/Open.Collections.Tests/Open.Collections.Tests.csproj @@ -4,7 +4,7 @@ net9.0 false - IDE0305;IDE0301;IDE0310; + IDE0305;IDE0301; From fb05267f3bcaffb7bde33975b71f10c1de63e8a4 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:24:52 -0800 Subject: [PATCH 06/18] Update project to v4.0.0 with various enhancements Removed unused directives and files. Added and improved XML documentation across multiple files. Enhanced and added new methods in Extensions.cs. Modified Shuffle method to accept optional Random parameter. Simplified SequenceEqual method. Updated project and package versions in Open.Collections.csproj. Refactored ReadWriteSynchronizedCollectionWrapper. Added missing assertion in BasicCollectionTests.cs. --- .../Benchmarks/SubsetBufferedBench.cs | 1 - source/ArrayPoolSegment.cs | 8 + source/Extensions.Permutations.cs | 4 +- source/Extensions.cs | 150 +++++++----------- source/Open.Collections.csproj | 6 +- source/SortDirection.cs | 2 + source/Subsets.cs | 3 + .../ReadWriteSynchronizedCollectionWrapper.cs | 22 +-- .../BasicCollectionTests.cs | 1 + .../Open.Collections.Tests/PermutorTests.cs | 1 - 10 files changed, 91 insertions(+), 107 deletions(-) diff --git a/benchmarking/Benchmarks/SubsetBufferedBench.cs b/benchmarking/Benchmarks/SubsetBufferedBench.cs index 9ec3efb..40525ce 100644 --- a/benchmarking/Benchmarks/SubsetBufferedBench.cs +++ b/benchmarking/Benchmarks/SubsetBufferedBench.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using System; -using System.Collections.Generic; using System.Linq; namespace Open.Collections.Benchmarks; diff --git a/source/ArrayPoolSegment.cs b/source/ArrayPoolSegment.cs index 74940e0..3834616 100644 --- a/source/ArrayPoolSegment.cs +++ b/source/ArrayPoolSegment.cs @@ -10,8 +10,16 @@ namespace Open.Collections; /// The type of the elements in the array. public readonly struct ArrayPoolSegment : IDisposable { + /// + /// The segment of the array. + /// public readonly ArraySegment Segment; + + /// + /// The used to rent the array. + /// public readonly ArrayPool? Pool; + private readonly bool _clear; /// diff --git a/source/Extensions.Permutations.cs b/source/Extensions.Permutations.cs index 690689a..8473ce2 100644 --- a/source/Extensions.Permutations.cs +++ b/source/Extensions.Permutations.cs @@ -1,12 +1,10 @@ -using Open.Disposable; -using System; +using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; -using System.Xml.Linq; namespace Open.Collections; diff --git a/source/Extensions.cs b/source/Extensions.cs index e70ce0c..9462ce6 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -313,19 +313,35 @@ public static void ForEach(this ISynchronizedCollection target, Cancellati }); } - public static IEnumerable Shuffle(this IEnumerable target) + /// + /// Randomizes the order of the source. + /// + public static IEnumerable Shuffle( + this IEnumerable target, Random? rnd = null) { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var r = new Random(); + var r = rnd ?? new Random(); return target.OrderBy(_ => r.Next()); } - // Ensures an optimized means of acquiring Any(); - public static bool HasAny(this IEnumerable source) => source.HasAtLeast(1); + /// + /// Tests the count of the source to see if there's any items. + /// + /// If the is null. + /// + /// First checks the type to see if a count can be aquired directly. If not, it will iterate through the source to count the items. + /// + public static bool HasAny(this IEnumerable source) + => source.HasAtLeast(1); + /// + /// Tests the count of the source to see if there's at least the number of items. + /// + /// If the is less than 1. + /// public static bool HasAtLeast(this IEnumerable source, int minimum) { if (source is null) @@ -338,8 +354,12 @@ public static bool HasAtLeast(this IEnumerable source, int minimum) { case T[] array: return array.Length >= minimum; + case IReadOnlyCollection collection: + return collection.Count >= minimum; case ICollection collection: return collection.Count >= minimum; + case ICollection collection: + return collection.Count >= minimum; } using IEnumerator? e = source.GetEnumerator(); @@ -351,6 +371,9 @@ public static bool HasAtLeast(this IEnumerable source, int minimum) return false; } + /// + /// Synchronizes enumerting by locking on the enumerator. + /// public static bool ConcurrentTryMoveNext(this IEnumerator source, out T item) { // Always lock on next to prevent concurrency issues. @@ -362,10 +385,14 @@ public static bool ConcurrentTryMoveNext(this IEnumerator source, out T it return true; } } + item = default!; return false; } + /// + /// Syncronizes enumerting by locking on the enumerator and invokes the provided handlers depending on if .MoveNext() was true. + /// public static bool ConcurrentMoveNext(this IEnumerator source, Action trueHandler, Action? falseHandler = null) { // Always lock on next to prevent concurrency issues. @@ -477,70 +504,6 @@ public static string ToConcatenatedString(this IEnumerable source, Func - /// Shortcut to String.Join() using "," as a default value. - /// - public static string Join(this string[] array, char separator) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); - Contract.EndContractBlock(); - - return string.Join(separator + string.Empty, array); - } - - public static string Join(this string[] array, string separator = ",") - { - if (array is null) - throw new ArgumentNullException(nameof(array)); - if (separator is null) - throw new ArgumentNullException(nameof(separator)); - Contract.EndContractBlock(); - - return string.Join(separator, array); - } - - /// - /// Concatenates a set of values into a single string using a character as a separator. - /// - public static string JoinToString(this IEnumerable source, char separator) - { - if (source is null) throw new ArgumentNullException(nameof(source)); - Contract.EndContractBlock(); - - var sb = new StringBuilder(); - using (IEnumerator? enumerator = source.GetEnumerator()) - { - if (enumerator.MoveNext()) - sb.Append(enumerator.Current); - while (enumerator.MoveNext()) - sb.Append(separator).Append(enumerator.Current); - } - - return sb.ToString(); - } - - /// - /// Concatenates set of values into a single string using another string as a separator. - /// - public static string JoinToString(this IEnumerable source, string separator) - { - if (source is null) throw new ArgumentNullException(nameof(source)); - if (separator is null) throw new ArgumentNullException(nameof(separator)); - Contract.EndContractBlock(); - - var sb = new StringBuilder(); - using (IEnumerator? enumerator = source.GetEnumerator()) - { - if (enumerator.MoveNext()) - sb.Append(enumerator.Current); - while (enumerator.MoveNext()) - sb.Append(separator).Append(enumerator.Current); - } - - return sb.ToString(); - } - /*public static T ValidateNotNull(this T target) { @@ -548,15 +511,12 @@ public static string JoinToString(this IEnumerable source, string separato return target; }*/ - public static Dictionary ToDictionary(this ParallelQuery> source) - where TKey : notnull - { - if (source is null) throw new ArgumentNullException(nameof(source)); - Contract.EndContractBlock(); - - return source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } - +#if NET9_0_OR_GREATER +#else + /// + /// Returns a dictionary from the source key-value pairs. + /// + /// If the is null. public static Dictionary ToDictionary(this IEnumerable> source) where TKey : notnull { @@ -565,7 +525,12 @@ public static Dictionary ToDictionary(this IEnumerab return source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } +#endif + /// + /// Converts an enumerable to a sorted dictionary. + /// + /// If the is null. public static SortedDictionary ToSortedDictionary(this IEnumerable> source) where TKey : notnull { @@ -579,6 +544,10 @@ public static SortedDictionary ToSortedDictionary(th return result; } + /// + /// Converts an enumerable to a sorted dictionary. + /// + /// If the , , or are null. public static SortedDictionary ToSortedDictionary(this IEnumerable source, Func keySelector, Func valueSelector) where TKey : notnull @@ -595,6 +564,10 @@ public static SortedDictionary ToSortedDictionary + /// Converts a grouping to a sorted dictionary. + /// + /// If the is null. public static SortedDictionary> ToSortedDictionary(this IEnumerable> source) where TKey : notnull { @@ -664,18 +637,8 @@ public static bool IsEquivalentTo(this IEnumerable source, IEnumerable if (source is null || target is null) return false; - if (source is IReadOnlyCollection sC && target is IReadOnlyCollection tC && sC.Count != tC.Count) - return false; - - using IEnumerator? enumSource = source.GetEnumerator(); - using IEnumerator? enumTarget = target.GetEnumerator(); - while (enumSource.MoveNext() && enumTarget.MoveNext()) - { - if (!enumSource.Current.Equals(enumTarget.Current)) - return false; - } - - return true; + // Both are not null, okay go. + return source.SequenceEqual(target); } /// @@ -843,6 +806,9 @@ private class OrderByInfo } #endregion + /// + /// A nullable struct version of FirstOrDefault. + /// public static T? NullableFirstOrDefault(this IEnumerable source) where T : struct { @@ -854,6 +820,9 @@ private class OrderByInfo return null; } + /// + /// Rotates through each enumerable and returns the next value until none are left. + /// public static IEnumerable Weave(this IEnumerable> source) { LinkedList>? queue = null; @@ -1067,6 +1036,9 @@ public static IEnumerator Preflight( yield return source.Current; } + /// + /// Executes an action when the begins enumeration. + /// public static IEnumerable BeforeGetEnumerator( this IEnumerable source, Action before) diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 46bd795..f41ea4c 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 3.2.0 + 4.0.0 MIT true @@ -42,8 +42,6 @@ True \ - - True @@ -51,7 +49,7 @@ - + diff --git a/source/SortDirection.cs b/source/SortDirection.cs index a2c2fac..0af13f7 100644 --- a/source/SortDirection.cs +++ b/source/SortDirection.cs @@ -1,7 +1,9 @@ namespace Open.Collections; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public enum SortDirection : sbyte { Ascending = +1, Descending = -1 } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/source/Subsets.cs b/source/Subsets.cs index 1c95962..a86a25c 100644 --- a/source/Subsets.cs +++ b/source/Subsets.cs @@ -7,6 +7,9 @@ namespace Open.Collections; +/// +/// Provides methods for generating subsets of a set. +/// public static class Subsets { internal static IEnumerable IndexesInternal(int sourceLength, int subsetLength, int[] buffer) diff --git a/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs b/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs index fc2ae02..ef54483 100644 --- a/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs @@ -8,17 +8,19 @@ namespace Open.Collections.Synchronized; -public class ReadWriteSynchronizedCollectionWrapper - : CollectionWrapper, ISynchronizedCollectionWrapper +/// +/// A disposable read-write synchronized wrapper for a collection. +/// +public class ReadWriteSynchronizedCollectionWrapper( + TCollection source, bool owner = false) + : CollectionWrapper(source, owner), ISynchronizedCollectionWrapper where TCollection : class, ICollection { + /// + /// The used for synchronization. + /// protected ReaderWriterLockSlim RWLock = new(LockRecursionPolicy.SupportsRecursion); // Support recursion for read -> write locks. - protected ReadWriteSynchronizedCollectionWrapper(TCollection source, bool owner = false) - : base(source, owner) - { - } - #region Implementation of ICollection /// @@ -118,10 +120,12 @@ public override Span CopyTo(Span span) #endregion #region Dispose - protected override void OnBeforeDispose() => + /// + protected override void OnBeforeDispose() // Give everything else a chance to finish up. - RWLock.TryWrite(1000, () => { }); + => RWLock.TryWrite(1000, () => { }); + /// protected override void OnDispose() { RWLock.Dispose(); diff --git a/testing/Open.Collections.Tests/BasicCollectionTests.cs b/testing/Open.Collections.Tests/BasicCollectionTests.cs index a7d32fd..fed78bb 100644 --- a/testing/Open.Collections.Tests/BasicCollectionTests.cs +++ b/testing/Open.Collections.Tests/BasicCollectionTests.cs @@ -131,6 +131,7 @@ public void Contains() Collection.Add(3); search = 2; } + Collection.Contains(search).Should().BeTrue(); if (Collection is not ISynchronizedCollectionWrapper> c) return; diff --git a/testing/Open.Collections.Tests/PermutorTests.cs b/testing/Open.Collections.Tests/PermutorTests.cs index a183465..6976da4 100644 --- a/testing/Open.Collections.Tests/PermutorTests.cs +++ b/testing/Open.Collections.Tests/PermutorTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using FluentAssertions; using Xunit; From 725104060f8d08de44f1285507f3dee7dd24c7e5 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:45:19 -0800 Subject: [PATCH 07/18] Updated references. --- source/Open.Collections.csproj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index f41ea4c..8369594 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.0.0 + 4.0.1 MIT true @@ -49,9 +49,10 @@ - + - + + From 36738b4855ca9c133d1f4878805742a4f0825db7 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Sun, 17 Nov 2024 15:12:54 -0800 Subject: [PATCH 08/18] Update Open.Threading --- source/Open.Collections.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 8369594..c344411 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.0.1 + 4.0.2 MIT true @@ -50,7 +50,7 @@ - + From 01ddfcc7fac8f8dbc5b9f87900f220c22ac76987 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Fri, 22 Nov 2024 07:32:49 -0800 Subject: [PATCH 09/18] Add new ArrayPoolSegment constructor and slicing methods - Updated `ArrayPoolSegment` in `ArrayPoolSegment.cs` with a new constructor and two `Slice` methods. - Added constants `MustBeAtLeast0` and `MustBeLessThanTheCount` in `Extensions.cs`. - Introduced slicing extension methods for `ArraySegment` under `#if NETSTANDARD2_0` in `Extensions.cs`. - Incremented version in `Open.Collections.csproj` from `4.0.2` to `4.0.3`. --- source/ArrayPoolSegment.cs | 30 ++++++++++++++++++++++++++- source/Extensions.cs | 37 ++++++++++++++++++++++++++++++++++ source/Open.Collections.csproj | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/source/ArrayPoolSegment.cs b/source/ArrayPoolSegment.cs index 3834616..25bf6b8 100644 --- a/source/ArrayPoolSegment.cs +++ b/source/ArrayPoolSegment.cs @@ -22,6 +22,19 @@ namespace Open.Collections; private readonly bool _clear; + /// + /// Constructs a new . + /// + public ArrayPoolSegment( + ArraySegment segment, + ArrayPool? pool = null, + bool clearArrayOnDispose = false) + { + Segment = segment; + Pool = pool; + _clear = clearArrayOnDispose; + } + /// /// Constructs a new from the . /// @@ -30,12 +43,27 @@ public ArrayPoolSegment( ArrayPool? pool = null, bool clearArrayOnDispose = false) { - _clear = clearArrayOnDispose; Pool = pool; T[]? array = pool?.Rent(length) ?? new T[length]; Segment = new(array, 0, length); + _clear = clearArrayOnDispose; } + /// + /// Forms a slice out of the segment + /// starting at the specified . + /// + public ArrayPoolSegment Slice(int index) + => new(Segment.Slice(index), Pool, _clear); + + /// + /// Forms a slice out of the segment + /// starting at the specified + /// and extending for the . + /// + public ArrayPoolSegment Slice(int index, int count) + => new(Segment.Slice(index, count), Pool, _clear); + /// /// Returns the array to the pool. /// diff --git a/source/Extensions.cs b/source/Extensions.cs index 9462ce6..db3a88a 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -1061,4 +1061,41 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + +#if NETSTANDARD2_0 + private const string MustBeAtLeast0 = "Must be at least 0."; + private const string MustBeLessThanTheCount = "Must be less than the count."; + + /// + /// Forms a slice out of the segment starting at the specified . + /// + public static ArraySegment Slice(this ArraySegment source, int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), index, MustBeAtLeast0); + if (index > source.Count) + throw new ArgumentOutOfRangeException(nameof(index), index, MustBeLessThanTheCount); + Contract.EndContractBlock(); + + return new ArraySegment(source.Array, source.Offset + index, source.Count - index); + } + + /// + /// Forms a slice out of the segment + /// starting at the specified + /// and extending for the. + /// + public static ArraySegment Slice(this ArraySegment source, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), index, MustBeAtLeast0); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), count, MustBeAtLeast0); + if (index > source.Count) + throw new ArgumentOutOfRangeException(nameof(index), index, MustBeLessThanTheCount); + Contract.EndContractBlock(); + + return new ArraySegment(source.Array, source.Offset + index, count); + } +#endif } diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index c344411..626857c 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.0.2 + 4.0.3 MIT true From cc90690fb46c97857ac15cb306cb21b41860a868 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:47:50 -0800 Subject: [PATCH 10/18] Add IEnumerable to ArrayPoolSegment and update Extensions - Implement IEnumerable in ArrayPoolSegment with GetEnumerator methods. - Add methods in Extensions.cs for creating ArraySegment with optional offset and count. - Update Slice method in Extensions.cs to use offset instead of index. - Increment Open.Collections.csproj version from 4.0.3 to 4.0.4. --- source/ArrayPoolSegment.cs | 8 +++- source/Extensions.cs | 78 +++++++++++++++++++++++++++------- source/Open.Collections.csproj | 2 +- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/source/ArrayPoolSegment.cs b/source/ArrayPoolSegment.cs index 25bf6b8..dfe7549 100644 --- a/source/ArrayPoolSegment.cs +++ b/source/ArrayPoolSegment.cs @@ -1,5 +1,7 @@ using System; using System.Buffers; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Open.Collections; @@ -8,7 +10,7 @@ namespace Open.Collections; /// Represents a segment of an array rented from an . /// /// The type of the elements in the array. -public readonly struct ArrayPoolSegment : IDisposable +public readonly struct ArrayPoolSegment : IDisposable, IEnumerable { /// /// The segment of the array. @@ -70,6 +72,10 @@ public ArrayPoolSegment Slice(int index, int count) /// public void Dispose() => Pool?.Return(Segment.Array!, _clear); + /// + public IEnumerator GetEnumerator() => Segment.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// /// Implicitly converts the to an . /// diff --git a/source/Extensions.cs b/source/Extensions.cs index db3a88a..538842f 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -11,6 +11,7 @@ using System.Dynamic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Channels; @@ -1062,40 +1063,85 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } -#if NETSTANDARD2_0 private const string MustBeAtLeast0 = "Must be at least 0."; - private const string MustBeLessThanTheCount = "Must be less than the count."; + private const string MustBeLessThanTheSize = "Must be less than the size."; + + /// + /// Creates a segment from the array. + /// + public static ArraySegment AsSegment(this T[] array) + => new(array); + + /// + /// Creates a segment from the array starting at the specified . + /// + public static ArraySegment AsSegment(this T[] array, int offset) + { + if (array is null) throw new ArgumentNullException(nameof(array)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeAtLeast0); + if (offset > array.Length) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeLessThanTheSize); + Contract.EndContractBlock(); + + return new(array, offset, array.Length - offset); + } + + /// + /// Creates a segment from the array starting at the specified and extending for the . + /// + public static ArraySegment AsSegment(this T[] array, int offset, int count) + { + if (array is null) throw new ArgumentNullException(nameof(array)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeAtLeast0); + Contract.EndContractBlock(); + + return new(array, offset, count); + } + +#if NETSTANDARD2_0 + /// + /// Enumerates the source segment. + /// + public static IEnumerator GetEnumerator(this ArraySegment source) + { + int start = source.Offset; + int end = start + source.Count; + for (int i = start; i < end; i++) + yield return source.Array[i]; + } /// - /// Forms a slice out of the segment starting at the specified . + /// Forms a slice out of the segment starting at the specified . /// - public static ArraySegment Slice(this ArraySegment source, int index) + public static ArraySegment Slice(this ArraySegment array, int offset) { - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), index, MustBeAtLeast0); - if (index > source.Count) - throw new ArgumentOutOfRangeException(nameof(index), index, MustBeLessThanTheCount); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeAtLeast0); + if (offset > array.Count) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeLessThanTheSize); Contract.EndContractBlock(); - return new ArraySegment(source.Array, source.Offset + index, source.Count - index); + return new ArraySegment(array.Array, array.Offset + offset, array.Count - offset); } /// /// Forms a slice out of the segment - /// starting at the specified + /// starting at the specified /// and extending for the. /// - public static ArraySegment Slice(this ArraySegment source, int index, int count) + public static ArraySegment Slice(this ArraySegment source, int offset, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), index, MustBeAtLeast0); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeAtLeast0); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, MustBeAtLeast0); - if (index > source.Count) - throw new ArgumentOutOfRangeException(nameof(index), index, MustBeLessThanTheCount); + if (offset > source.Count) + throw new ArgumentOutOfRangeException(nameof(offset), offset, MustBeLessThanTheSize); Contract.EndContractBlock(); - return new ArraySegment(source.Array, source.Offset + index, count); + return new ArraySegment(source.Array, source.Offset + offset, count); } #endif } diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 626857c..2e54cb3 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.0.3 + 4.0.4 MIT true From 31a0e6165fc2851cb056f1217f51347908ff318f Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:27:13 -0800 Subject: [PATCH 11/18] Reformat so extra line space where required by standard. --- benchmarking/Benchmarks/DictionaryParallelBenchmark.cs | 1 + benchmarking/Benchmarks/TrieBenchmarks.cs | 7 +++++++ source/DictionaryToHashSetWrapper.cs | 1 + source/Extensions.Generic.cs | 1 + source/Extensions.Subsets.cs | 2 ++ source/Extensions.SubsetsProgressive.cs | 2 ++ source/Extensions.cs | 5 +++++ source/Subsets.cs | 1 + source/Synchronized/ConcurrentList.cs | 2 ++ .../Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs | 1 + 10 files changed, 23 insertions(+) diff --git a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs index f45e2f4..71d6be2 100644 --- a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs +++ b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs @@ -20,6 +20,7 @@ protected override IEnumerable TestOnceInternal() int testSize; checked { testSize = (int)TestSize; } + const int mixSize = 100; var c = (IDictionary)Param(); yield return TimedResult.Measure("Fill (.Add(keyValuePair)) (In Parallel)", diff --git a/benchmarking/Benchmarks/TrieBenchmarks.cs b/benchmarking/Benchmarks/TrieBenchmarks.cs index 4bfde35..2c173ff 100644 --- a/benchmarking/Benchmarks/TrieBenchmarks.cs +++ b/benchmarking/Benchmarks/TrieBenchmarks.cs @@ -70,6 +70,7 @@ public static IEnumerable GenerateTree(int depth, int nodeCount, Stack { yield return key; } + stack.Pop(); } } @@ -82,6 +83,7 @@ public int TrieLookup() { trie.TryGetValue(key.Item1, out result); } + return result; } @@ -94,6 +96,7 @@ public int TrieWalkLookup() string[] k = key.Item1; result = trie.GetChild(k[0]).GetChild(k[1]).GetChild(k[2]).GetChild(k[3]).Value; } + return result; } @@ -105,6 +108,7 @@ public int TrieLookupWithToArray() { trie.TryGetValue(key.Item1.ToArray(), out result); } + return result; } @@ -116,6 +120,7 @@ public int DictionaryLookup() { result = dictionary[key.Item2]; } + return result; } @@ -128,6 +133,7 @@ public int DictionaryLookupKeyConcat() string[] k = key.Item1; result = dictionary[$"{k[0]}/{k[1]}/{k[2]}/{k[3]}"]; } + return result; } @@ -139,6 +145,7 @@ public int DictionaryLookupWithJoin() { result = dictionary[string.Join("/", key.Item1)]; } + return result; } diff --git a/source/DictionaryToHashSetWrapper.cs b/source/DictionaryToHashSetWrapper.cs index 5c43dbf..5b15166 100644 --- a/source/DictionaryToHashSetWrapper.cs +++ b/source/DictionaryToHashSetWrapper.cs @@ -37,6 +37,7 @@ public virtual bool Add(T item) { return false; } + return true; } diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index e8cfaa1..5a3cb7a 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -85,6 +85,7 @@ public static int Remove(this ICollection target, IEnumerable values) count++; } } + return count; } diff --git a/source/Extensions.Subsets.cs b/source/Extensions.Subsets.cs index e267eff..1149dc1 100644 --- a/source/Extensions.Subsets.cs +++ b/source/Extensions.Subsets.cs @@ -37,6 +37,7 @@ static IEnumerable> SubsetsCore(IReadOnlyList source, int count, Me buffer.Span[0] = e; yield return buffer; } + yield break; } @@ -146,6 +147,7 @@ static IEnumerable> SubsetsCore(ReadOnlyMemory source, int count, M buffer.Span[0] = source.Span[i]; yield return buffer; } + yield break; } diff --git a/source/Extensions.SubsetsProgressive.cs b/source/Extensions.SubsetsProgressive.cs index ee66325..5eb29ca 100644 --- a/source/Extensions.SubsetsProgressive.cs +++ b/source/Extensions.SubsetsProgressive.cs @@ -35,6 +35,7 @@ static IEnumerable SubsetsProgressiveCore(IReadOnlyList source, int coun buffer[0] = e; yield return buffer; } + yield break; } @@ -66,6 +67,7 @@ static IEnumerable SubsetsProgressiveCore(IReadOnlyList source, int coun yield return buffer; } + ++n; } } diff --git a/source/Extensions.cs b/source/Extensions.cs index 538842f..30db7b6 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -369,6 +369,7 @@ public static bool HasAtLeast(this IEnumerable source, int minimum) if (--minimum == 0) return true; } + return false; } @@ -405,6 +406,7 @@ public static bool ConcurrentMoveNext(this IEnumerator source, Action t return true; } } + falseHandler?.Invoke(); return false; } @@ -724,6 +726,7 @@ private static IQueryable ApplyOrderBy(IQueryable collection, OrderByIn expr = Expression.Property(expr, pi); type = pi.PropertyType; } + Type? delegateTypeSource = typeof(Func<,>); //var delegateTypeSourceArgs = delegateTypeSource.GetGenericArguments(); @@ -790,6 +793,7 @@ private static IEnumerable ParseOrderBy(StringSegment orderBy) $"Invalid OrderBy string '{item}'. Order By Format: Property, Property2 ASC, Property2 DESC"); } } + i++; } @@ -906,6 +910,7 @@ public static int IndexOf(this T[] source, T value) if (source[i]?.Equals(value) ?? value is null) return i; } + return -1; } diff --git a/source/Subsets.cs b/source/Subsets.cs index a86a25c..3a1be16 100644 --- a/source/Subsets.cs +++ b/source/Subsets.cs @@ -21,6 +21,7 @@ internal static IEnumerable IndexesInternal(int sourceLength, int subsetL buffer[0] = i; yield return buffer; } + yield break; } diff --git a/source/Synchronized/ConcurrentList.cs b/source/Synchronized/ConcurrentList.cs index 8051e81..33e4520 100644 --- a/source/Synchronized/ConcurrentList.cs +++ b/source/Synchronized/ConcurrentList.cs @@ -71,8 +71,10 @@ private List Grow() capacity = int.MaxValue; break; } + capacity *= 2; } + list.Capacity = capacity; return list; } diff --git a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs index f0f55f5..da94b82 100644 --- a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs @@ -26,6 +26,7 @@ public virtual TValue this[TKey key] InternalSource[key] = value; return; } + using var write = RWLock.WriteLock(); InternalSource[key] = value; } From 9981ee6383b5ac141433d0e5a69daf1169b81f13 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:27:39 -0800 Subject: [PATCH 12/18] Enhance dictionary extensions and add null checks - Added `where TKey : notnull` constraint to methods in `Extensions.Generic.Synchronized.cs` and `Extensions.Generic.cs`. - Added null checks for parameters to throw `ArgumentNullException`. - Added `Contract.EndContractBlock()` for code contracts. - Updated XML comments with `` for thread-safety notes. - Introduced conditional compilation for .NET 9.0+ to optimize dictionary operations. - Added `TryUpdate` method in `Extensions.Generic.cs` for conditional updates. - Added XML documentation to `SortDirection` enum in `SortDirection.cs`. --- source/Extensions.Generic.Synchronized.cs | 11 +++ source/Extensions.Generic.cs | 95 ++++++++++++++++++++--- source/SortDirection.cs | 12 ++- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/source/Extensions.Generic.Synchronized.cs b/source/Extensions.Generic.Synchronized.cs index 6c7bd9d..6bf660e 100644 --- a/source/Extensions.Generic.Synchronized.cs +++ b/source/Extensions.Generic.Synchronized.cs @@ -97,6 +97,7 @@ public static void RegisterSynchronized(this ICollection target, T value) /// public static T AddOrUpdateSynchronized(this IDictionary target, TKey key, T value, Func updateValueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -134,6 +135,7 @@ public static T AddOrUpdateSynchronized(this IDictionary targe public static T AddOrUpdateSynchronized(this IDictionary target, TKey key, Func newValueFactory, Func updateValueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -190,6 +192,7 @@ public static void AddSynchronized(this ICollection target, T value) /// Thread safe shortcut for adding a value to list within a dictionary. /// public static void AddToSynchronized(this IDictionary> c, TKey key, TValue value) + where TKey : notnull { if (c is null) throw new ArgumentNullException(nameof(c)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -203,6 +206,7 @@ public static void AddToSynchronized(this IDictionary public static void EnsureDefaultSynchronized(this IDictionary target, TKey key, T defaultValue) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -218,6 +222,7 @@ public static void EnsureDefaultSynchronized(this IDictionary /// public static void EnsureDefaultSynchronized(this IDictionary target, TKey key, Func defaultValueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -239,6 +244,7 @@ public static T GetOrAddSynchronized( T value, int millisecondsTimeout = SYNC_TIMEOUT_DEFAULT_MILLISECONDS, bool throwsOnTimeout = true) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -269,6 +275,7 @@ public static T GetOrAddSynchronized( TKey key, Func valueFactory, int millisecondsTimeout = SYNC_TIMEOUT_DEFAULT_MILLISECONDS) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -309,6 +316,7 @@ public static bool TryAddSynchronized( TKey key, T value, int millisecondsTimeout = SYNC_TIMEOUT_DEFAULT_MILLISECONDS) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -337,6 +345,7 @@ public static bool TryAddSynchronized( TKey key, Func valueFactory, int millisecondsTimeout = SYNC_TIMEOUT_DEFAULT_MILLISECONDS) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -364,6 +373,7 @@ public static bool TryRemoveSynchronized( this IDictionary target, TKey key, int millisecondsTimeout = SYNC_TIMEOUT_DEFAULT_MILLISECONDS) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -389,6 +399,7 @@ public static bool TryRemoveSynchronized( TKey key, out T value, int millisecondsTimeout = SYNC_TIMEOUT_DEFAULT_MILLISECONDS) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index 5a3cb7a..9511b3f 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -27,8 +27,8 @@ public static void SetOrRemove( /// /// Adds a value to list only if it does not exist. - /// NOT THREAD SAFE: Use only when a collection local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static void Register(this ICollection target, T value) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -93,11 +93,12 @@ public static int Remove(this ICollection target, IEnumerable values) /// Shortcut for adding a value or updating based on exising value. /// If no value exists, it adds the provided value. /// If a value exists, it sets the value using the updateValueFactory. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static T AddOrUpdate(this IDictionary target, TKey key, T value, T updateValue) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -116,10 +117,11 @@ public static T AddOrUpdate(this IDictionary target, TKey key, /// Shortcut for adding a value or updating based on exising value. /// If no value exists, it adds the provided value. /// If a value exists, it sets the value using the updateValueFactory. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static T AddOrUpdate(this IDictionary target, TKey key, T value, Func updateValueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -139,11 +141,12 @@ public static T AddOrUpdate(this IDictionary target, TKey key, /// Shortcut for adding a value or updating based on exising value. /// If no value exists, it adds the value using the newValueFactory. /// If a value exists, it sets the value using the updateValueFactory. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static T AddOrUpdate(this IDictionary target, TKey key, Func newValueFactory, Func updateValueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -164,6 +167,7 @@ public static T AddOrUpdate(this IDictionary target, TKey key, /// Thread safe shortcut for adding a value to list within a dictionary. /// public static void AddTo(this IDictionary> c, TKey key, TValue value) + where TKey : notnull { if (c is null) throw new ArgumentNullException(nameof(c)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -175,9 +179,10 @@ public static void AddTo(this IDictionary> c, /// /// Shortcut for ensuring a cacheKey contains a action. If no action exists, it adds the provided defaultValue. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static void EnsureDefault(this IDictionary target, TKey key, T defaultValue) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -189,8 +194,8 @@ public static void EnsureDefault(this IDictionary target, TKey /// /// Shortcut for ensuring a cacheKey contains a Value. If no action exists, it adds it using the provided defaultValueFactory. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static void EnsureDefault(this IDictionary target, TKey key, Func defaultValueFactory) { @@ -209,6 +214,7 @@ public static void EnsureDefault(this IDictionary target, TKey public static T GetOrDefault( this IDictionary target, TKey key) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -224,6 +230,7 @@ public static T GetOrDefault( this IDictionary target, TKey key, T defaultValue) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -239,6 +246,7 @@ public static T GetOrDefault( this IDictionary target, TKey key, Func valueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); @@ -250,38 +258,103 @@ public static T GetOrDefault( /// /// Tries to acquire a value from the dictionary. If no value is present it adds it using the valueFactory response. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static T GetOrAdd( this IDictionary target, TKey key, Func valueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); if (valueFactory is null) throw new ArgumentNullException(nameof(valueFactory)); Contract.EndContractBlock(); - if (!target.TryGetValue(key, out T? value)) +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, key, out bool exists); + if(!exists) val = valueFactory(key); + return val!; + } +#endif + if (!target.TryGetValue(key, out var value)) target.Add(key, value = valueFactory(key)); + return value; } /// /// Tries to acquire a value from the dictionary. If no value is present it adds the value provided. - /// NOT THREAD SAFE: Use only when a dictionary local or is assured single threaded. /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. public static T GetOrAdd( this IDictionary target, - TKey key, - T value) + TKey key, T value) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, key, out bool exists); + if (!exists) val = value; + return val!; + } +#endif + if (!target.TryGetValue(key, out T? v)) target.Add(key, v = value); return v; } + + /// + /// Tries to update an existing value in the dictionary. + /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. + /// + /// if the value was overwritten; + /// if there was no original value + /// or if is and the values are equal. + /// + public static bool TryUpdate( + this IDictionary target, + TKey key, T value, + bool compareExisting = false) + where TKey : notnull + { + if (target is null) throw new ArgumentNullException(nameof(target)); + if (key is null) throw new ArgumentNullException(nameof(key)); + Contract.EndContractBlock(); + +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrNullRef(d, key); + if(System.Runtime.CompilerServices.Unsafe.IsNullRef(ref val)) + return false; + + if(compareExisting && !AreEqual(val, value)) + return false; + + val = value; + return true; + } +#endif + + if (!target.TryGetValue(key, out var v)) + return false; + + if (compareExisting && !AreEqual(v, value)) + return false; + + target[key] = value; + return true; + + static bool AreEqual(T a, T b) => a is null ? b is null : a.Equals(b); + } } diff --git a/source/SortDirection.cs b/source/SortDirection.cs index 0af13f7..ad59f15 100644 --- a/source/SortDirection.cs +++ b/source/SortDirection.cs @@ -1,9 +1,17 @@ namespace Open.Collections; -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +/// +/// Represents the direction of sorting. +/// public enum SortDirection : sbyte { + /// + /// Ascending Order + /// Ascending = +1, + + /// + /// Descending Order + /// Descending = -1 } -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member From 7bb9c1ec78eb2ef93775eec5ac3f74b86ff86e15 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:01:00 -0800 Subject: [PATCH 13/18] Add new methods and optimize for .NET 9.0 - Added `AddRange` method for `ReadOnlySpan` to `ICollection`. - Modified `AddThese` to use `ReadOnlySpan` for `more` parameter. - Simplified `AddThese` with a `foreach` loop. - Enhanced `UpdateOrAdd`, `UpdateOrAddWithFactory`, and `GetOrAddWithFactory` using `CollectionsMarshal.GetValueRefOrNullRef` for .NET 9.0. - Marked `EnsureDefault` methods as obsolete; added `TryAdd` method. - Removed old `EnsureDefault` methods, replaced with `TryAdd`. - Updated documentation comments to reflect changes. --- source/Extensions.Generic.cs | 114 ++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index 9511b3f..b0fea88 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -56,15 +56,31 @@ public static void AddRange( target.Add(value); } + /// + public static void AddRange( + this ICollection target, + ReadOnlySpan values) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + Contract.EndContractBlock(); + + foreach (T value in values) + target.Add(value); + } + /// /// Adds each value to the end of the collection. /// +#if NET9_0_OR_GREATER + public static void AddThese(this ICollection target, T a, T b, params ReadOnlySpan more) +#else public static void AddThese(this ICollection target, T a, T b, params T[] more) +#endif { target.Add(a); target.Add(b); - if (more.Length != 0) - target.AddRange(more); + foreach (T value in more) + target.Add(value); } /// @@ -80,8 +96,7 @@ public static int Remove(this ICollection target, IEnumerable values) { foreach (T? value in values) { - if ( - target.Remove(value)) + if (target.Remove(value)) count++; } } @@ -104,6 +119,16 @@ public static T AddOrUpdate(this IDictionary target, TKey key, if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrNullRef(d, key); + return System.Runtime.CompilerServices.Unsafe.IsNullRef(ref val) + ? (val = value) + : (val = updateValue); + } +#endif + T valueUsed; if (target.TryGetValue(key, out _)) target[key] = valueUsed = updateValue; @@ -128,6 +153,16 @@ public static T AddOrUpdate(this IDictionary target, TKey key, if (updateValueFactory is null) throw new ArgumentNullException(nameof(updateValueFactory)); Contract.EndContractBlock(); +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrNullRef(d, key); + return System.Runtime.CompilerServices.Unsafe.IsNullRef(ref val) + ? (val = value) + : (val = updateValueFactory(key, val)); + } +#endif + T valueUsed; if (target.TryGetValue(key, out T? old)) target[key] = valueUsed = updateValueFactory(key, old); @@ -154,6 +189,16 @@ public static T AddOrUpdate(this IDictionary target, TKey key, if (updateValueFactory is null) throw new ArgumentNullException(nameof(updateValueFactory)); Contract.EndContractBlock(); +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrNullRef(d, key); + return System.Runtime.CompilerServices.Unsafe.IsNullRef(ref val) + ? (val = newValueFactory(key)) + : (val = updateValueFactory(key, val)); + } +#endif + T valueUsed; if (target.TryGetValue(key, out T? old)) target[key] = valueUsed = updateValueFactory(key, old); @@ -164,7 +209,7 @@ public static T AddOrUpdate(this IDictionary target, TKey key, } /// - /// Thread safe shortcut for adding a value to list within a dictionary. + /// Shortcut for adding a value to list within a dictionary. /// public static void AddTo(this IDictionary> c, TKey key, TValue value) where TKey : notnull @@ -178,34 +223,73 @@ public static void AddTo(this IDictionary> c, } /// - /// Shortcut for ensuring a cacheKey contains a action. If no action exists, it adds the provided defaultValue. + /// Shortcut for ensuring a cacheKey contains a action. If no value exists, it adds the provided defaultValue. /// /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. + [Obsolete("Use TryAdd instead.")] public static void EnsureDefault(this IDictionary target, TKey key, T defaultValue) where TKey : notnull + => TryAdd(target, key, defaultValue); + + /// + /// Shortcut for ensuring a cacheKey contains a Value. If no value exists, it adds it using the provided defaultValueFactory. + /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. + [Obsolete("Use TryAdd instead.")] + public static void EnsureDefault(this IDictionary target, TKey key, + Func defaultValueFactory) + where TKey : notnull + => TryAdd(target, key, defaultValueFactory); + + /// + /// Attempts to add a value to a dictionary if it does not already exist. + /// + /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. + public static bool TryAdd(this IDictionary target, TKey key, T value) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); Contract.EndContractBlock(); - if (!target.ContainsKey(key)) - target.Add(key, defaultValue); +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, key, out bool exists); + if (!exists) val = value; + return !exists; + } +#endif + if (target.ContainsKey(key)) + return false; + + target.Add(key, value); + return true; } - /// - /// Shortcut for ensuring a cacheKey contains a Value. If no action exists, it adds it using the provided defaultValueFactory. - /// - /// NOT THREAD SAFE: Use only when a dictionary is assured to be single threaded. - public static void EnsureDefault(this IDictionary target, TKey key, + /// + public static bool TryAdd(this IDictionary target, TKey key, Func defaultValueFactory) + where TKey : notnull { if (target is null) throw new ArgumentNullException(nameof(target)); if (key is null) throw new ArgumentNullException(nameof(key)); if (defaultValueFactory is null) throw new ArgumentNullException(nameof(defaultValueFactory)); Contract.EndContractBlock(); - if (!target.ContainsKey(key)) - target.Add(key, defaultValueFactory(key)); +#if NET9_0_OR_GREATER + if (target is Dictionary d) + { + ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, key, out bool exists); + if (!exists) val = defaultValueFactory(key); + return !exists; + } +#endif + if (target.ContainsKey(key)) + return false; + + target.Add(key, defaultValueFactory(key)); + return true; } /// From eef313c8d0c6705e185df0014fd6f606d0d71e6e Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Wed, 22 Jan 2025 07:33:19 -0800 Subject: [PATCH 14/18] Enhance collections and improve performance Added `using System;` and `using System.Runtime.CompilerServices;` to support new features and optimizations. Introduced conditional compilation for .NET 9.0+ in `CollectionWrapper.cs` and `IAddMultiple.cs` to use `ReadOnlySpan`. Added new methods and overloads to `CollectionWrapper` for adding multiple items and ranges, including `ReadOnlySpan` support. Added XML documentation comments to improve maintainability. Renamed `TryTakeWhileCpre` to `TryTakeWhileCore` in `Extensions.ConcurrentBag.cs` for consistency and added methods for trimming and clearing `ConcurrentBag`. Added helper methods and classes like `KeyValuePair.Create` and `ReadOnlyCollectionAdapter`. Enhanced `ListWrapper` and `TrackedCollectionWrapper` with additional constructors, methods, and properties. Marked some methods as `[Obsolete]` to guide developers towards more efficient alternatives. Improved exception handling and null checks for robustness. --- source/CollectionWrapper.cs | 23 ++++++- source/DictionaryToHashSetWrapper.cs | 6 ++ source/DictionaryWrapper.cs | 3 + source/Extensions.Combinations.cs | 3 + source/Extensions.ConcurrentBag.cs | 20 +++++- source/Extensions.Generic.cs | 4 ++ source/IAddMultiple.cs | 28 ++++++-- source/KeyValuePair.cs | 6 ++ source/ListWrapper.cs | 10 +++ source/ReadOnlyCollectionAdapter.cs | 7 ++ source/ReadOnlyCollectionWrapper.cs | 1 + .../Synchronized/TrackedCollectionWrapper.cs | 66 ++++++++++++++++++- 12 files changed, 164 insertions(+), 13 deletions(-) diff --git a/source/CollectionWrapper.cs b/source/CollectionWrapper.cs index de8a90a..6c91cf1 100644 --- a/source/CollectionWrapper.cs +++ b/source/CollectionWrapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -49,8 +50,13 @@ public virtual void Add(T item) AddInternal(in item); } +#if NET9_0_OR_GREATER + /// + public virtual void AddThese(T item1, T item2, params ReadOnlySpan items) +#else /// public virtual void AddThese(T item1, T item2, params T[] items) +#endif { AssertIsAlive(); AddInternal(in item1); @@ -67,6 +73,19 @@ public virtual void AddThese(T item1, T item2, params T[] items) /// The items to add. [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void AddRange(IEnumerable items) + { + AssertIsAlive(); + if (items is null) return; + foreach (var i in items) + AddInternal(in i); + } + + /// +#if NET9_0_OR_GREATER + [OverloadResolutionPriority(1)] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void AddRange(ReadOnlySpan items) { AssertIsAlive(); foreach (var i in items) @@ -86,5 +105,5 @@ public virtual bool Remove(T item) /// public override bool IsReadOnly => InternalSource.IsReadOnly; - #endregion +#endregion } diff --git a/source/DictionaryToHashSetWrapper.cs b/source/DictionaryToHashSetWrapper.cs index 5b15166..6527d63 100644 --- a/source/DictionaryToHashSetWrapper.cs +++ b/source/DictionaryToHashSetWrapper.cs @@ -5,11 +5,17 @@ namespace Open.Collections; +/// +/// A wrapper for a to implement . +/// [method: ExcludeFromCodeCoverage] public class DictionaryToHashSetWrapper( IDictionary source) : ISet { + /// + /// The internal source dictionary. + /// protected readonly IDictionary InternalSource = source; /// diff --git a/source/DictionaryWrapper.cs b/source/DictionaryWrapper.cs index 086fc93..486cbc4 100644 --- a/source/DictionaryWrapper.cs +++ b/source/DictionaryWrapper.cs @@ -34,18 +34,21 @@ protected override TValue GetValueInternal(TKey key) protected override void SetValueInternal(TKey key, TValue value) => InternalSource[key] = value; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override ICollection GetKeys() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(InternalSource.Keys), () => InternalSource.Count); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override ICollection GetValues() => new ReadOnlyCollectionAdapter( ThrowIfDisposed(InternalSource.Values), () => InternalSource.Count); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void AddInternal(TKey key, TValue value) => InternalSource.Add(key, value); diff --git a/source/Extensions.Combinations.cs b/source/Extensions.Combinations.cs index b738b44..6488a0f 100644 --- a/source/Extensions.Combinations.cs +++ b/source/Extensions.Combinations.cs @@ -150,6 +150,9 @@ public static IEnumerable> CombinationsDistinct(this IEnumera return source.Count == 0 ? Enumerable.Empty>() : CombinationsCore(source, true, buffer); } + /// + /// Enumerates all possible combinations of values. + /// [Obsolete("Deprecated in favor of using .Subsets(length) or .Combinations(length) depending on intent.")] public static IEnumerable Combinations(this IEnumerable elements, int length, bool uniqueOnly) { diff --git a/source/Extensions.ConcurrentBag.cs b/source/Extensions.ConcurrentBag.cs index 8278c21..5cc2b0d 100644 --- a/source/Extensions.ConcurrentBag.cs +++ b/source/Extensions.ConcurrentBag.cs @@ -9,14 +9,17 @@ namespace Open.Collections; public static partial class Extensions { + /// + /// Attempts to take items from the while the is true. + /// public static IEnumerable TryTakeWhile(this ConcurrentBag target, Func, bool> predicate) { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - return TryTakeWhileCpre(target, predicate); + return TryTakeWhileCore(target, predicate); - static IEnumerable TryTakeWhileCpre(ConcurrentBag target, Func, bool> predicate) + static IEnumerable TryTakeWhileCore(ConcurrentBag target, Func, bool> predicate) { while (!target.IsEmpty && predicate(target) && target.TryTake(out T? value)) { @@ -25,6 +28,7 @@ static IEnumerable TryTakeWhileCpre(ConcurrentBag target, Func public static IEnumerable TryTakeWhile(this ConcurrentBag target, Func predicate) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -33,6 +37,9 @@ public static IEnumerable TryTakeWhile(this ConcurrentBag target, Func< return TryTakeWhile(target, _ => predicate()); } + /// + /// Trims the to the specified . + /// public static void Trim(this ConcurrentBag target, int maxSize) { foreach (T? _ in TryTakeWhile(target, t => t.Count > maxSize)) @@ -40,6 +47,9 @@ public static void Trim(this ConcurrentBag target, int maxSize) } } + /// + /// Trims the to the specified and calls the for each trimmed item. + /// public static Task TrimAsync(this ConcurrentBag target, int maxSize, Action handler) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -52,5 +62,9 @@ public static Task TrimAsync(this ConcurrentBag target, int maxSize, Actio ); } - public static Task ClearAsync(this ConcurrentBag target, Action handler) => TrimAsync(target, 0, handler); + /// + /// Clears the and calls the for each item. + /// + public static Task ClearAsync(this ConcurrentBag target, Action handler) + => TrimAsync(target, 0, handler); } diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index b0fea88..bf8c8ba 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; namespace Open.Collections; @@ -57,6 +58,9 @@ public static void AddRange( } /// +#if NET9_0_OR_GREATER + [OverloadResolutionPriority(1)] +#endif public static void AddRange( this ICollection target, ReadOnlySpan values) diff --git a/source/IAddMultiple.cs b/source/IAddMultiple.cs index d962ef8..8eb7d38 100644 --- a/source/IAddMultiple.cs +++ b/source/IAddMultiple.cs @@ -1,19 +1,33 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Open.Collections; + +/// +/// Represents a collection that can add multiple items. +/// public interface IAddMultiple { + /// + /// Adds all the items in to this collection. + /// + /// The items to add. + void AddRange(IEnumerable items); + // Note: "AddThese" is the name because Add can have multiple signatures. /// Adds more than one item. /// First item to add. /// Additional item to add. /// Extended param items to add. - void AddThese(T item1, T item2, params T[] items); +#if NET9_0_OR_GREATER + void AddThese(T item1, T item2, params System.ReadOnlySpan items); - /// - /// Adds all the items in to this collection. - /// - /// The items to add. - void AddRange(IEnumerable items); + /// /> + [OverloadResolutionPriority(1)] + void AddRange(ReadOnlySpan items); +#else + void AddThese(T item1, T item2, params T[] items); +#endif } diff --git a/source/KeyValuePair.cs b/source/KeyValuePair.cs index 4ba8f52..404c012 100644 --- a/source/KeyValuePair.cs +++ b/source/KeyValuePair.cs @@ -4,8 +4,14 @@ namespace Open.Collections; +/// +/// A static helper class for creating instances. +/// public static class KeyValuePair { + /// + /// Creates a new . + /// [ExcludeFromCodeCoverage] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static KeyValuePair Create(TKey key, TValue value) => new(key, value); diff --git a/source/ListWrapper.cs b/source/ListWrapper.cs index fb5dacd..4bcaaa2 100644 --- a/source/ListWrapper.cs +++ b/source/ListWrapper.cs @@ -4,6 +4,9 @@ namespace Open.Collections; +/// +/// A wrapper for that allows for easy extension. +/// public class ListWrapper( TList source, bool owner = false) : CollectionWrapper(source, owner), IList @@ -33,15 +36,22 @@ public virtual void RemoveAt(int index) => InternalSource.RemoveAt(index); } +/// +/// A wrapper for that allows for easy extension. +/// [ExcludeFromCodeCoverage] public class ListWrapper : ListWrapper> { + /// + /// Initializes a new instance of the class. + /// public ListWrapper(IList source, bool owner = false) : base(source, owner) { } + /// public ListWrapper(int capacity = 0) : base(new List(capacity)) { diff --git a/source/ReadOnlyCollectionAdapter.cs b/source/ReadOnlyCollectionAdapter.cs index 739345f..b80964f 100644 --- a/source/ReadOnlyCollectionAdapter.cs +++ b/source/ReadOnlyCollectionAdapter.cs @@ -7,6 +7,9 @@ namespace Open.Collections; +/// +/// A read-only collection adapter that can be used to wrap an existing collection. +/// [method: ExcludeFromCodeCoverage] public sealed class ReadOnlyCollectionAdapter( IEnumerable source, Func getCount) @@ -18,10 +21,14 @@ public sealed class ReadOnlyCollectionAdapter( ? item => c.Contains(item) : item => source.Contains(item); + /// + /// Initializes a new instance of the class. + /// [ExcludeFromCodeCoverage] public ReadOnlyCollectionAdapter(IReadOnlyCollection source) : this(source, () => source.Count) { } + /// [ExcludeFromCodeCoverage] public ReadOnlyCollectionAdapter(ICollection source) : this(source, () => source.Count) { } diff --git a/source/ReadOnlyCollectionWrapper.cs b/source/ReadOnlyCollectionWrapper.cs index ae87d2a..baffb91 100644 --- a/source/ReadOnlyCollectionWrapper.cs +++ b/source/ReadOnlyCollectionWrapper.cs @@ -122,6 +122,7 @@ public virtual void Export(ICollection to) => to.AddRange(InternalSource); #region Dispose + /// [ExcludeFromCodeCoverage] protected override void OnDispose() { diff --git a/source/Synchronized/TrackedCollectionWrapper.cs b/source/Synchronized/TrackedCollectionWrapper.cs index e88a3b1..75e94ae 100644 --- a/source/Synchronized/TrackedCollectionWrapper.cs +++ b/source/Synchronized/TrackedCollectionWrapper.cs @@ -10,13 +10,23 @@ namespace Open.Collections.Synchronized; +/// +/// A wrapper for a collection that tracks changes and provides synchronization. +/// public class TrackedCollectionWrapper : ModificationSynchronizedBase, ICollection, IAddMultiple, ISynchronizedCollectionWrapper where TCollection : class, ICollection { + /// + /// The internal source collection. + /// protected TCollection? InternalUnsafeSource; + + /// + /// The internal source collection when not disposed. + /// protected TCollection InternalSource => InternalUnsafeSource ?? throw new ObjectDisposedException(GetType().ToString()); @@ -30,6 +40,9 @@ protected TCollection InternalSource /// public event EventHandler>? Changed; + /// + /// True if there are listeners for the event. + /// protected bool HasChangedListeners => Changed is not null; /// @@ -37,14 +50,21 @@ protected TCollection InternalSource /// public event EventHandler? Cleared; + /// + /// Initializes a new instance of the class. + /// [ExcludeFromCodeCoverage] public TrackedCollectionWrapper(TCollection collection, ModificationSynchronizer? sync = null) : base(sync) => InternalUnsafeSource = collection ?? throw new ArgumentNullException(nameof(collection)); + /// + /// Initializes a new instance of the class. + /// [ExcludeFromCodeCoverage] public TrackedCollectionWrapper(TCollection collection, out ModificationSynchronizer sync) : base(out sync) => InternalUnsafeSource = collection ?? throw new ArgumentNullException(nameof(collection)); + /// [ExcludeFromCodeCoverage] protected override ModificationSynchronizer InitSync(object? sync = null) { @@ -52,6 +72,7 @@ protected override ModificationSynchronizer InitSync(object? sync = null) return new ReadWriteModificationSynchronizer(sync as ReaderWriterLockSlim); } + /// [ExcludeFromCodeCoverage] protected override void OnDispose() { @@ -65,6 +86,11 @@ protected override void OnDispose() private void ThrowIfDisposedInternal() => base.AssertIsAlive(); private Action? _throwIfDisposed; + + /// + /// The delegate to invoke to throw an exception if disposed. + /// + [ExcludeFromCodeCoverage] protected Action ThrowIfDisposedDelegate => _throwIfDisposed ??= ThrowIfDisposedInternal; @@ -76,18 +102,30 @@ public int Count return InternalSource.Count; }); + /// + /// Adds an item to the internal collection. + /// [ExcludeFromCodeCoverage] protected virtual void AddInternal(T item) => InternalSource.Add(item); + /// + /// Invoked after the collection has been modified. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void OnModified() => Modified?.Invoke(this, EventArgs.Empty); + /// + /// Invoked after a change or group of changes has been made. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void OnChanged(ItemChange change, T item, int version) => Changed?.Invoke(this, change.CreateArgs(item, version)); + /// + /// Invoked after a change or group of changes has been made. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected virtual void OnChanged(ItemChange change, TIndex index, T item, int version) => Changed?.Invoke(this, change.CreateArgs(index, item, version)); @@ -100,6 +138,9 @@ private void OnAdded(T item, int version) private void OnRemoved(T item, int version) => OnChanged(ItemChange.Removed, item, version); + /// + /// Invoked after the collection has been cleared. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected virtual void OnCleared(int version) => Cleared?.Invoke(this, version); @@ -114,8 +155,11 @@ public void Add(T item) return true; }, version => OnAdded(item, version)); - +#if NET9_0_OR_GREATER + /// +#else /// +#endif public void AddThese(T item1, T item2, params T[] items) => Sync!.Modifying(AssertIsAliveDelegate, () => @@ -163,6 +207,23 @@ public void AddRange(IEnumerable items) }); } +#if NET9_0_OR_GREATER + /// + [Obsolete("This method has to make a copy of items. Use the local AddThese(T, T, T[]) instead.")] + [OverloadResolutionPriority(-1)] + public void AddThese(T item1, T item2, params ReadOnlySpan items) + => AddThese(item1, item2, items.ToArray()); + + /// + [Obsolete("This method has to make a copy of items. Use the local AddThese(T, T, T[]) instead.")] + [OverloadResolutionPriority(-1)] + public void AddRange(ReadOnlySpan items) + => AddThese(default!, default!, items.ToArray()); +#endif + + /// + /// Clears the internal collection. + /// [ExcludeFromCodeCoverage] protected virtual void ClearInternal() => InternalUnsafeSource!.Clear(); @@ -273,6 +334,9 @@ public virtual bool IfNotContains(T item, Action action) public T[] Snapshot() => Sync!.Reading(() => InternalSource.ToArray()); + /// + /// Synchronizes exporting the internal collection to the specified collection. + /// public void Export(ICollection to) => Sync!.Reading(() => to.AddRange(InternalSource)); } From fc17dc263d68f2f9634a855c6250bc5bb81b5b83 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Wed, 22 Jan 2025 07:37:16 -0800 Subject: [PATCH 15/18] Bump version: Open.Collections 4.0.4 to 4.1.0 Updated the version number in Open.Collections.csproj from 4.0.4 to 4.1.0, indicating the introduction of new features, improvements, or fixes in accordance with semantic versioning practices. --- source/Open.Collections.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 2e54cb3..8c92d65 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -18,7 +18,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.0.4 + 4.1.0 MIT true From ca1de3e81ec3e0ff9a556af953eb7aa14610683a Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Wed, 22 Jan 2025 07:39:29 -0800 Subject: [PATCH 16/18] Add conditional compilation for AddThese method Introduce conditional compilation for AddThese method in both LockSynchronizedCollectionWrapper and ReadWriteSynchronizedCollectionWrapper classes. Use ReadOnlySpan for .NET 9.0+ and T[] for earlier versions. Remove extra #endregion directive in LockSynchronizedCollectionWrapper. --- source/Synchronized/LockSynchronizedCollectionWrapper.cs | 6 +++++- .../Synchronized/ReadWriteSynchronizedCollectionWrapper.cs | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/Synchronized/LockSynchronizedCollectionWrapper.cs b/source/Synchronized/LockSynchronizedCollectionWrapper.cs index 41a24e3..1f938ae 100644 --- a/source/Synchronized/LockSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/LockSynchronizedCollectionWrapper.cs @@ -32,7 +32,11 @@ public override void Add(T item) } /// +#if NET9_0_OR_GREATER + public override void AddThese(T item1, T item2, params ReadOnlySpan items) +#else public override void AddThese(T item1, T item2, params T[] items) +#endif { lock (Sync) { @@ -79,7 +83,7 @@ public override bool Remove(T item) lock (Sync) return base.Remove(item); } - #endregion +#endregion /// [ExcludeFromCodeCoverage] diff --git a/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs b/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs index ef54483..1494430 100644 --- a/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs @@ -33,7 +33,11 @@ public override void Add(T item) } /// +#if NET9_0_OR_GREATER + public override void AddThese(T item1, T item2, params ReadOnlySpan items) +#else public override void AddThese(T item1, T item2, params T[] items) +#endif { using var write = RWLock.WriteLock(); AddInternal(item1); From c14cf5c62e4fd2e75439b85b713fb707a72b04ad Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Fri, 18 Apr 2025 08:29:51 -0700 Subject: [PATCH 17/18] Refactor codebase for clarity and consistency - Updated `.editorconfig` with new spell check diagnostics. - Improved readability in `DictionaryParallelBenchmark.cs` by adjusting loop formatting. - Cleaned up using directives across multiple files, including `ArrayPoolSegment.cs` and `CollectionWrapper.cs`. - Enhanced documentation in `ConcurrentHashSet.cs`, `ItemChangedEventArgs.cs`, and various `Extensions` files. - Updated methods in `DictionaryToHashSetWrapper.cs` and `IndexedDictionary.cs` for clarity. - Added synchronization methods in `TrackedCollectionWrapper.cs` and improved thread safety documentation. - Fixed spelling errors and improved clarity in comments and documentation throughout the codebase. - Added new package reference in `Open.Collections.csproj` for `PurelySharp.Attributes`. - Enhanced test readability in `PermutorTests.cs` and corrected examples in `Readme.md`. --- .editorconfig | 9 +++ .../Benchmarks/DictionaryParallelBenchmark.cs | 4 +- source/ArrayPoolSegment.cs | 5 +- source/CollectionWrapper.cs | 15 ++--- source/ConcurrentHashSet.cs | 4 +- source/DictionaryToHashSetWrapper.cs | 7 +-- source/DictionaryWrapper.cs | 6 +- source/DictionaryWrapperBase.cs | 6 +- source/Extensions.ByteArray.cs | 4 +- source/Extensions.Combinations.cs | 12 +--- source/Extensions.ConcurrentBag.cs | 7 +-- source/Extensions.ConcurrentDictionary.cs | 7 +-- source/Extensions.Generic.Synchronized.cs | 22 +++---- source/Extensions.Generic.cs | 19 +++--- source/Extensions.Permutations.cs | 7 +-- source/Extensions.Stream.cs | 6 +- source/Extensions.Subsets.cs | 5 +- source/Extensions.SubsetsProgressive.cs | 5 +- source/Extensions.cs | 36 +++++------- source/IAddMultiple.cs | 6 +- source/IIndexedDictionary.cs | 5 +- source/IndexedDictionary.cs | 14 ++--- source/ItemChangedEventArgs.cs | 58 +++++++++++++++++-- source/KeyValuePair.cs | 5 +- source/LazyList.cs | 10 +--- source/LazyListUnsafe.cs | 6 +- source/LinkedList/ILinkedList.cs | 4 +- source/LinkedList/LinkedList.Standard.cs | 16 +++-- source/ListWrapper.cs | 6 +- source/NonGeneric/Extensions.Synchronized.cs | 7 +-- source/NonGeneric/Extensions.cs | 4 +- source/Open.Collections.csproj | 4 +- source/OrderedDictionary.cs | 11 +--- source/Queue/Extensions.cs | 2 - source/Queue/IQueue.cs | 7 ++- source/Queue/Queue.Concurrent.cs | 5 +- source/Queue/Queue.Standard.cs | 5 +- source/ReadOnlyCollectionAdapter.cs | 7 +-- source/ReadOnlyCollectionWrapper.cs | 4 -- source/Subsets.cs | 5 +- source/Synchronized/ConcurrentList.cs | 6 -- .../Synchronized/ISynchronizedCollection.cs | 8 +-- .../ISynchronizedCollectionWrapper.cs | 8 +-- .../LockSynchronizedCollectionWrapper.cs | 6 +- .../LockSynchronizedDictionaryWrapper.cs | 9 ++- .../Synchronized/LockSynchronizedHashSet.cs | 10 +--- .../LockSynchronizedIndexedDictionary.cs | 7 ++- .../LockSynchronizedLinkedList.cs | 11 +++- source/Synchronized/LockSynchronizedList.cs | 7 +-- .../LockSynchronizedListWrapper.cs | 9 +-- .../LockSynchronizedOrderedDictionary.cs | 7 ++- source/Synchronized/LockSynchronizedQueue.cs | 4 +- .../ReadWriteSynchronizedCollectionWrapper.cs | 5 -- .../ReadWriteSynchronizedDictionaryWrapper.cs | 4 -- .../ReadWriteSynchronizedHashSet.cs | 8 +-- .../ReadWriteSynchronizedLinkedList.cs | 4 +- .../Synchronized/ReadWriteSynchronizedList.cs | 7 +-- .../ReadWriteSynchronizedListWrapper.cs | 2 - source/Synchronized/Readme.md | 2 +- .../Synchronized/TrackedCollectionWrapper.cs | 6 -- .../Synchronized/TrackedDictionaryWrapper.cs | 3 - .../TrackedIndexedDictionaryWrapper.cs | 20 +++++-- source/Synchronized/TrackedListWrapper.cs | 5 +- source/_Imports.cs | 4 ++ .../Open.Collections.Tests/PermutorTests.cs | 10 ++-- 65 files changed, 235 insertions(+), 314 deletions(-) create mode 100644 source/_Imports.cs diff --git a/.editorconfig b/.editorconfig index 4ab8963..a9c857c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -223,6 +223,15 @@ dotnet_naming_style.begins_with_i.capitalization = pascal_case # IDE0057: Use range operator dotnet_diagnostic.IDE0057.severity = silent +# VSSpell001: Spell Check +dotnet_diagnostic.VSSpell001.severity = none + +# VSSpell002: Spell Check +dotnet_diagnostic.VSSpell002.severity = none + +# IDE0306: Simplify collection initialization +dotnet_diagnostic.IDE0306.severity = silent + dotnet_diagnostic.CS1591.severity = suggestion csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent diff --git a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs index 71d6be2..dd876d7 100644 --- a/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs +++ b/benchmarking/Benchmarks/DictionaryParallelBenchmark.cs @@ -30,8 +30,8 @@ protected override IEnumerable TestOnceInternal() () => Parallel.For(0, testSize, i => c[i] = _items[i].Value)); #if DEBUG - for (int i = 0; i < testSize; ++i) - Debug.Assert(c[i] == _items[i].Value); + for (int i = 0; i < testSize; ++i) + Debug.Assert(c[i] == _items[i].Value); #endif //yield return TimedResult.Measure("Enumerate (8 times)", () => diff --git a/source/ArrayPoolSegment.cs b/source/ArrayPoolSegment.cs index dfe7549..7abf862 100644 --- a/source/ArrayPoolSegment.cs +++ b/source/ArrayPoolSegment.cs @@ -1,8 +1,5 @@ -using System; -using System.Buffers; +using System.Buffers; using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections; diff --git a/source/CollectionWrapper.cs b/source/CollectionWrapper.cs index 6c91cf1..2e1b664 100644 --- a/source/CollectionWrapper.cs +++ b/source/CollectionWrapper.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// /// A disposable wrapper for a collection. @@ -18,7 +13,7 @@ public class CollectionWrapper( /// The underlying object used for synchronization. /// #if NET9_0_OR_GREATER - protected readonly System.Threading.Lock Sync = new(); + protected readonly Lock Sync = new(); #else protected readonly object Sync = new(); #endif @@ -28,7 +23,7 @@ public class CollectionWrapper( /// This is exposed to allow for more complex synchronization operations. /// #if NET9_0_OR_GREATER - public System.Threading.Lock SyncRoot => Sync; + public Lock SyncRoot => Sync; #else public object SyncRoot => Sync; #endif @@ -66,7 +61,7 @@ public virtual void AddThese(T item1, T item2, params T[] items) } /// - /// Adds mutliple items to the collection. + /// Adds multiple items to the collection. /// It's important to avoid locking for too long so an array is used to add multiple items. /// An enumerable is potentially slow as it may be yielding to a process. /// @@ -105,5 +100,5 @@ public virtual bool Remove(T item) /// public override bool IsReadOnly => InternalSource.IsReadOnly; -#endregion + #endregion } diff --git a/source/ConcurrentHashSet.cs b/source/ConcurrentHashSet.cs index 68cf492..3132bde 100644 --- a/source/ConcurrentHashSet.cs +++ b/source/ConcurrentHashSet.cs @@ -1,6 +1,4 @@ using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections; @@ -12,7 +10,7 @@ public sealed class ConcurrentHashSet : DictionaryToHashSetWrapper where T : notnull { /// - /// Construct a new instance with optoinal initial values. + /// Construct a new instance with optional initial values. /// public ConcurrentHashSet(IEnumerable? intialValues = null) : base(new ConcurrentDictionary()) diff --git a/source/DictionaryToHashSetWrapper.cs b/source/DictionaryToHashSetWrapper.cs index 6527d63..76b98e1 100644 --- a/source/DictionaryToHashSetWrapper.cs +++ b/source/DictionaryToHashSetWrapper.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Collections; namespace Open.Collections; @@ -71,7 +68,7 @@ public virtual bool Add(T item) /// Returns a copy of the underlying keys. /// [ExcludeFromCodeCoverage] - public HashSet ToHashSet() => new(InternalSource.Keys); + public HashSet ToHashSet() => [.. InternalSource.Keys]; /// [ExcludeFromCodeCoverage] diff --git a/source/DictionaryWrapper.cs b/source/DictionaryWrapper.cs index 486cbc4..f075f69 100644 --- a/source/DictionaryWrapper.cs +++ b/source/DictionaryWrapper.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// [ExcludeFromCodeCoverage] diff --git a/source/DictionaryWrapperBase.cs b/source/DictionaryWrapperBase.cs index 3bc81b6..61042fd 100644 --- a/source/DictionaryWrapperBase.cs +++ b/source/DictionaryWrapperBase.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// /// A base class for wrapping a collection as a dictionary. diff --git a/source/Extensions.ByteArray.cs b/source/Extensions.ByteArray.cs index bbdc3ad..d82b3e8 100644 --- a/source/Extensions.ByteArray.cs +++ b/source/Extensions.ByteArray.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.Contracts; -using System.Text; +using System.Text; namespace Open.Collections; diff --git a/source/Extensions.Combinations.cs b/source/Extensions.Combinations.cs index 6488a0f..cc70231 100644 --- a/source/Extensions.Combinations.cs +++ b/source/Extensions.Combinations.cs @@ -1,10 +1,4 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Runtime.CompilerServices; +using System.Buffers; namespace Open.Collections; @@ -244,7 +238,7 @@ public static IEnumerable CombinationsDistinct(this IEnumerable eleme /// Enumerates all possible combinations of values. /// Results can be different permutations of another set. /// - /// [0, 0], [0, 1], [1, 0], [1, 1] where [0, 1] and [1, 0] are a different permutatation of the same set. + /// [0, 0], [0, 1], [1, 0], [1, 1] where [0, 1] and [1, 0] are a different permutation of the same set. /// The elements to draw from. public static IEnumerable Combinations(this IEnumerable elements) { @@ -258,7 +252,7 @@ public static IEnumerable Combinations(this IEnumerable elements) /// Enumerates all possible distinct set combinations. /// In contrast a set that has its items reordered is not distinct from the original. /// - /// [0, 0], [0, 1], [1, 1] where [1, 0] is not included as it is not a disticnt set from [0, 1]. + /// [0, 0], [0, 1], [1, 1] where [1, 0] is not included as it is not a distinct set from [0, 1]. /// The elements to draw from. public static IEnumerable CombinationsDistinct(this IEnumerable elements) { diff --git a/source/Extensions.ConcurrentBag.cs b/source/Extensions.ConcurrentBag.cs index 5cc2b0d..d88a3ba 100644 --- a/source/Extensions.ConcurrentBag.cs +++ b/source/Extensions.ConcurrentBag.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Concurrent; namespace Open.Collections; diff --git a/source/Extensions.ConcurrentDictionary.cs b/source/Extensions.ConcurrentDictionary.cs index a3c347e..b716dce 100644 --- a/source/Extensions.ConcurrentDictionary.cs +++ b/source/Extensions.ConcurrentDictionary.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics.Contracts; -using System.Threading.Tasks; +using System.Collections.Concurrent; namespace Open.Collections; public static partial class Extensions { /// - /// Shortcut for removeing a value without needing an 'out' parameter. + /// Shortcut for removing a value without needing an 'out' parameter. /// public static bool TryRemove(this ConcurrentDictionary target, TKey key) where TKey : notnull diff --git a/source/Extensions.Generic.Synchronized.cs b/source/Extensions.Generic.Synchronized.cs index 6bf660e..bd17e43 100644 --- a/source/Extensions.Generic.Synchronized.cs +++ b/source/Extensions.Generic.Synchronized.cs @@ -1,8 +1,4 @@ using Open.Threading; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; namespace Open.Collections; @@ -18,7 +14,7 @@ internal static void ValidateMillisecondsTimeout(int? millisecondsTimeout) } /// - /// Thread safe value for syncronizing acquiring a value from a generic dictionary. + /// Thread safe value for synchronizing acquiring a value from a generic dictionary. /// /// True if a value was acquired. public static bool TryGetValueSynchronized( @@ -47,7 +43,7 @@ public static bool TryGetValueSynchronized( } /// - /// Attempts to acquire a specified type from a generic dictonary. + /// Attempts to acquire a specified type from a generic dictionary. /// public static TValue GetValueSynchronized( this IDictionary target, TKey key) @@ -62,7 +58,7 @@ public static TValue GetValueSynchronized( } /// - /// Attempts to acquire a specified type from a generic dictonary or returns a default value. + /// Attempts to acquire a specified type from a generic dictionary or returns a default value. /// public static TValue GetValueSynchronized( this IDictionary target, TKey key, TValue defaultValue) @@ -77,7 +73,7 @@ public static TValue GetValueSynchronized( } /// - /// Thread safe value for syncronizing adding a value to list only if it does not exist. + /// Thread safe value for synchronizing adding a value to list only if it does not exist. /// public static void RegisterSynchronized(this ICollection target, T value) { @@ -91,7 +87,7 @@ public static void RegisterSynchronized(this ICollection target, T value) } /// - /// Thread safe shortcut for adding a value or updating based on exising value. + /// Thread safe shortcut for adding a value or updating based on existing value. /// If no value exists, it adds the provided value. /// If a value exists, it sets the value using the updateValueFactory. /// @@ -119,7 +115,7 @@ public static T AddOrUpdateSynchronized(this IDictionary targe } else { - // Fallback for if the action changed. Will end up locking the collection but what can we do.. :( + // Fall-back for if the action changed. Will end up locking the collection but what can we do.. :( ThreadSafety.SynchronizeWrite(target, () => valueUsed = target.AddOrUpdate(key, value, updateValueFactory)); } }); @@ -128,7 +124,7 @@ public static T AddOrUpdateSynchronized(this IDictionary targe } /// - /// Thread safe shortcut for adding a value or updating based on exising value. + /// Thread safe shortcut for adding a value or updating based on existing value. /// If no value exists, it adds the value using the newValueFactory. /// If a value exists, it sets the value using the updateValueFactory. /// @@ -287,7 +283,7 @@ public static T GetOrAddSynchronized( // Note, the following sync read is on the TARGET and not the key. See below. bool condition(bool _) => !ThreadSafety.SynchronizeRead(target, () => target.TryGetValue(key, out result)); - // Once a per value write lock is established, execute the scheduler, and syncronize adding... + // Once a per value write lock is established, execute the scheduler, and synchronize adding... void render() => target.GetOrAddSynchronized(key, result = valueFactory(key), millisecondsTimeout); // This will queue up subsequent reads for the same value. @@ -297,7 +293,7 @@ public static T GetOrAddSynchronized( // ^^^ What actually happens... // 1) Value is checked for without a lock and if acquired returns it using the 'condition' query. // 2) Value is checked for WITH a lock and if acquired returns it using the 'condition' query. - // 3) A localized lock is acquired for the the key which tells other _threads to wait while the value is generated and added. + // 3) A localized lock is acquired for the key which tells other _threads to wait while the value is generated and added. // 4) Value is checked for without a lock and if acquired returns it using the 'condition' query. // 5) The value is then rendered using the ensureRendered query without locking the entire collection. This allows for other values to be added. // 6) The rendered value is then used to add to the collection if the value is missing, locking the collection if an add is necessary. diff --git a/source/Extensions.Generic.cs b/source/Extensions.Generic.cs index bf8c8ba..9c7505a 100644 --- a/source/Extensions.Generic.cs +++ b/source/Extensions.Generic.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; public static partial class Extensions { @@ -109,7 +104,7 @@ public static int Remove(this ICollection target, IEnumerable values) } /// - /// Shortcut for adding a value or updating based on exising value. + /// Shortcut for adding a value or updating based on existing value. /// If no value exists, it adds the provided value. /// If a value exists, it sets the value using the updateValueFactory. /// @@ -143,7 +138,7 @@ public static T AddOrUpdate(this IDictionary target, TKey key, } /// - /// Shortcut for adding a value or updating based on exising value. + /// Shortcut for adding a value or updating based on existing value. /// If no value exists, it adds the provided value. /// If a value exists, it sets the value using the updateValueFactory. /// @@ -177,7 +172,7 @@ public static T AddOrUpdate(this IDictionary target, TKey key, } /// - /// Shortcut for adding a value or updating based on exising value. + /// Shortcut for adding a value or updating based on existing value. /// If no value exists, it adds the value using the newValueFactory. /// If a value exists, it sets the value using the updateValueFactory. /// @@ -363,7 +358,7 @@ public static T GetOrAdd( if (target is Dictionary d) { ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, key, out bool exists); - if(!exists) val = valueFactory(key); + if (!exists) val = valueFactory(key); return val!; } #endif @@ -423,10 +418,10 @@ public static bool TryUpdate( if (target is Dictionary d) { ref var val = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrNullRef(d, key); - if(System.Runtime.CompilerServices.Unsafe.IsNullRef(ref val)) + if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref val)) return false; - if(compareExisting && !AreEqual(val, value)) + if (compareExisting && !AreEqual(val, value)) return false; val = value; diff --git a/source/Extensions.Permutations.cs b/source/Extensions.Permutations.cs index 8473ce2..583aea4 100644 --- a/source/Extensions.Permutations.cs +++ b/source/Extensions.Permutations.cs @@ -1,10 +1,5 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Linq; +using System.Buffers; using System.Numerics; -using System.Runtime.CompilerServices; namespace Open.Collections; diff --git a/source/Extensions.Stream.cs b/source/Extensions.Stream.cs index dcd970f..e25479b 100644 --- a/source/Extensions.Stream.cs +++ b/source/Extensions.Stream.cs @@ -1,8 +1,4 @@ -using System; -using System.Buffers; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using System.Buffers; namespace Open.Collections; diff --git a/source/Extensions.Subsets.cs b/source/Extensions.Subsets.cs index 1149dc1..5e73434 100644 --- a/source/Extensions.Subsets.cs +++ b/source/Extensions.Subsets.cs @@ -1,7 +1,4 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics.Contracts; +using System.Buffers; namespace Open.Collections; diff --git a/source/Extensions.SubsetsProgressive.cs b/source/Extensions.SubsetsProgressive.cs index 5eb29ca..429aec4 100644 --- a/source/Extensions.SubsetsProgressive.cs +++ b/source/Extensions.SubsetsProgressive.cs @@ -1,7 +1,4 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics.Contracts; +using System.Buffers; namespace Open.Collections; diff --git a/source/Extensions.cs b/source/Extensions.cs index 30db7b6..b06bb7e 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -1,21 +1,13 @@ using Microsoft.Extensions.Primitives; using Open.Text; -using System; using System.Buffers; using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.Contracts; using System.Dynamic; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; using System.Text; -using System.Threading; using System.Threading.Channels; -using System.Threading.Tasks; namespace Open.Collections; @@ -149,12 +141,14 @@ public static T[] ToArrayOfLength(this T[] source, int length) } /// - /// Coerces to a collection either by matching the type or by creating a new array. + /// If the source is already a collection, it will be returned as is. If not, it will be converted to a list. /// - public static ICollection ToCollection(this IEnumerable source) - => source is null - ? null! - : source as ICollection ?? source.ToArray(); + public static ICollection ToCollection(this IEnumerable? source) + => source is null ? [] : source as ICollection ?? source.ToList(); + + /// + public static IReadOnlyCollection ToReadOnlyCollection(this IEnumerable source) + => source is null ? [] : source as IReadOnlyCollection ?? Array.AsReadOnly(source.ToArray()); /// /// Iterates over the source in parallel. @@ -275,11 +269,11 @@ public static void ForEach(this ISynchronizedCollection target, Action } /// - /// Iterates over the source and can be cancelled. + /// Iterates over the source and can be canceled. /// /// The or are null. #pragma warning disable IDE0079 // Remove unnecessary suppression - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Allows for simpler implementation. Other methods cover non-cancellable case.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Allows for simpler implementation. Other methods cover non-cancelable case.")] #pragma warning restore IDE0079 // Remove unnecessary suppression public static void ForEach(this IEnumerable target, CancellationToken token, Action closure) { @@ -295,7 +289,7 @@ public static void ForEach(this IEnumerable target, CancellationToken toke } /// - /// Iterates over the source with a lock and can be cancelled. + /// Iterates over the source with a lock and can be canceled. /// /// The or are null. public static void ForEach(this ISynchronizedCollection target, CancellationToken token, Action closure) @@ -333,7 +327,7 @@ public static IEnumerable Shuffle( /// /// If the is null. /// - /// First checks the type to see if a count can be aquired directly. If not, it will iterate through the source to count the items. + /// First checks the type to see if a count can be acquired directly. If not, it will iterate through the source to count the items. /// public static bool HasAny(this IEnumerable source) => source.HasAtLeast(1); @@ -374,7 +368,7 @@ public static bool HasAtLeast(this IEnumerable source, int minimum) } /// - /// Synchronizes enumerting by locking on the enumerator. + /// Synchronizes enumerating by locking on the enumerator. /// public static bool ConcurrentTryMoveNext(this IEnumerator source, out T item) { @@ -393,7 +387,7 @@ public static bool ConcurrentTryMoveNext(this IEnumerator source, out T it } /// - /// Syncronizes enumerting by locking on the enumerator and invokes the provided handlers depending on if .MoveNext() was true. + /// Synchronizes enumerating by locking on the enumerator and invokes the provided handlers depending on if .MoveNext() was true. /// public static bool ConcurrentMoveNext(this IEnumerator source, Action trueHandler, Action? falseHandler = null) { @@ -742,7 +736,7 @@ private static IQueryable ApplyOrderBy(IQueryable collection, OrderByIn ? "OrderBy" : "OrderByDescending"; - //TODO: apply caching to the generic methodsinfos? + //TODO: apply caching to the generic method-infos? System.Reflection.MethodInfo[]? methods = typeof(Queryable).GetMethods(); System.Reflection.MethodInfo? r1 = methods .Single(method => method.Name == methodName @@ -847,7 +841,7 @@ public static IEnumerable Weave(this IEnumerable> source) if (queue is null) yield break; - // Start by getting the first enuerator if it exists. + // Start by getting the first enumerator if it exists. LinkedListNode>? n = queue.First; while (n is not null) { diff --git a/source/IAddMultiple.cs b/source/IAddMultiple.cs index 8eb7d38..e25520c 100644 --- a/source/IAddMultiple.cs +++ b/source/IAddMultiple.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// /// Represents a collection that can add multiple items. diff --git a/source/IIndexedDictionary.cs b/source/IIndexedDictionary.cs index 69c3e20..0509ab7 100644 --- a/source/IIndexedDictionary.cs +++ b/source/IIndexedDictionary.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace Open.Collections; +namespace Open.Collections; /// /// Represents a generic items of key/value pairs that are ordered independently of the key and value. diff --git a/source/IndexedDictionary.cs b/source/IndexedDictionary.cs index 24dec00..4636e83 100644 --- a/source/IndexedDictionary.cs +++ b/source/IndexedDictionary.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// /// A minimal implementation of that is inherently not thread safe. @@ -82,8 +75,13 @@ public override int Count } #if NETSTANDARD2_0 +#pragma warning disable IDE0079 // Remove unnecessary suppression [SuppressMessage("Roslynator", "RCS1242:Do not pass non-read-only struct by read-only reference.", Justification = "KeyValuePairs are not truly readonly until NET Standard 2.1.")] +#pragma warning restore IDE0079 // Remove unnecessary suppression #endif + // Even though in later versions of .NET KeyValuePair is readonly, + // We allow the `in` keyword to keep the API consistent and easy to implement. + // This doesn't change the performance of the code. private int AddToLists(in KeyValuePair kvp) { int i = _entries.Count; diff --git a/source/ItemChangedEventArgs.cs b/source/ItemChangedEventArgs.cs index 0e2455e..9a2e70d 100644 --- a/source/ItemChangedEventArgs.cs +++ b/source/ItemChangedEventArgs.cs @@ -1,41 +1,87 @@ -using System; - -// ReSharper disable NotAccessedField.Global -// ReSharper disable MemberCanBeProtected.Global - -namespace Open.Collections; +namespace Open.Collections; +/// +/// The possible changes to an item. +/// public enum ItemChange { + /// + /// Default value indicating no change. + /// None, + + /// + /// The item was added to the collection. + /// Added, + + /// + /// The item was removed from the collection. + /// Removed, + + /// + /// The item was inserted into the collection. + /// Inserted, + + /// + /// The item was replaced in the collection. + /// Modified } +/// +/// Event arguments for item changes. +/// public class ItemChangedEventArgs( ItemChange action, T value, int version) : EventArgs { + /// + /// The action that caused the event to be raised. + /// public readonly ItemChange Change = action; + + /// + /// The value that was changed to. + /// public readonly T Value = value; + + /// + /// The version of the collection at the time of the change. + /// public readonly int Version = version; } +/// +/// Event arguments for item changes with an index. +/// public class ItemChangedEventArgs( ItemChange action, TIndex index, TValue value, int version) : ItemChangedEventArgs(action, value, version) { + /// + /// The index of the item that was changed. + /// public readonly TIndex Index = index; } +/// +/// A static helper class for creating instances. +/// public static class ItemChangeEventArgs { + /// + /// Creates a new instance. + /// public static ItemChangedEventArgs CreateArgs( this ItemChange change, T value, int version) => new(change, value, version); + /// + /// Creates a new instance. + /// public static ItemChangedEventArgs CreateArgs( this ItemChange change, TIndex index, TValue value, int version) => new(change, index, value, version); diff --git a/source/KeyValuePair.cs b/source/KeyValuePair.cs index 404c012..02a2092 100644 --- a/source/KeyValuePair.cs +++ b/source/KeyValuePair.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - namespace Open.Collections; /// @@ -12,6 +8,7 @@ public static class KeyValuePair /// /// Creates a new . /// + [Pure] [ExcludeFromCodeCoverage] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static KeyValuePair Create(TKey key, TValue value) => new(key, value); diff --git a/source/LazyList.cs b/source/LazyList.cs index 4441323..b85ce38 100644 --- a/source/LazyList.cs +++ b/source/LazyList.cs @@ -4,10 +4,6 @@ */ using Open.Threading; -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Threading; namespace Open.Collections; @@ -55,7 +51,7 @@ protected override bool EnsureIndex(int maxIndex) return true; // This is where the fun begins... - // Mutliple threads can be out of sync (probably through a memory barrier) + // Multiple threads can be out of sync (probably through a memory barrier) // And a sync read operation must be done to ensure safety. int count = Sync.Read(() => Cached.Count); if (maxIndex < count) @@ -73,7 +69,7 @@ protected override bool EnsureIndex(int maxIndex) // This very well could be a simple lock{} statement but the ReaderWriterLockSlim recursion protection is actually quite useful. using var uLock = Sync.UpgradableReadLock(); - // Note: Within an upgradable read, other reads pile up. + // Note: Within an upgradeable read, other reads pile up. int c = Cached.Count; if (_safeCount != c) // Always do comparisons outside of interlocking first. @@ -90,7 +86,7 @@ protected override bool EnsureIndex(int maxIndex) while (Enumerator.MoveNext()) { if (Cached.Count == int.MaxValue) - throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); + throw new Exception("Reached maximum contents for a single list. Cannot memoize further."); Cached.Add(Enumerator.Current); diff --git a/source/LazyListUnsafe.cs b/source/LazyListUnsafe.cs index a532da1..8d7b165 100644 --- a/source/LazyListUnsafe.cs +++ b/source/LazyListUnsafe.cs @@ -4,10 +4,6 @@ */ using Open.Disposable; -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Threading; namespace Open.Collections; @@ -154,7 +150,7 @@ protected virtual bool EnsureIndex(int maxIndex) while (Enumerator.MoveNext()) { if (Cached.Count == int.MaxValue) - throw new Exception("Reached maximium contents for a single list. Cannot memoize further."); + throw new Exception("Reached maximum contents for a single list. Cannot memoize further."); Cached.Add(Enumerator.Current); diff --git a/source/LinkedList/ILinkedList.cs b/source/LinkedList/ILinkedList.cs index 3ca042d..0caf549 100644 --- a/source/LinkedList/ILinkedList.cs +++ b/source/LinkedList/ILinkedList.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Open.Collections; +namespace Open.Collections; /// /// An interface for a linked list. diff --git a/source/LinkedList/LinkedList.Standard.cs b/source/LinkedList/LinkedList.Standard.cs index e286b1e..de52a17 100644 --- a/source/LinkedList/LinkedList.Standard.cs +++ b/source/LinkedList/LinkedList.Standard.cs @@ -1,17 +1,23 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections; +namespace Open.Collections; +/// +/// The container for . +/// public static class LinkedList { + /// + /// A standard implementation that allows for the interface. + /// + /// This allows for other linked lists to be used interchangeably. public sealed class Standard : LinkedList, ILinkedList { + /// [ExcludeFromCodeCoverage] - public Standard() + public Standard() : base() { } + /// [ExcludeFromCodeCoverage] public Standard(IEnumerable initial) : base(initial) { diff --git a/source/ListWrapper.cs b/source/ListWrapper.cs index 4bcaaa2..e7dd336 100644 --- a/source/ListWrapper.cs +++ b/source/ListWrapper.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// /// A wrapper for that allows for easy extension. diff --git a/source/NonGeneric/Extensions.Synchronized.cs b/source/NonGeneric/Extensions.Synchronized.cs index 6e32848..df2a3e6 100644 --- a/source/NonGeneric/Extensions.Synchronized.cs +++ b/source/NonGeneric/Extensions.Synchronized.cs @@ -1,12 +1,11 @@ using Open.Threading; -using System; using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; namespace Open.Collections.NonGeneric; +/// +/// Extensions for non-generic collections. +/// [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Global")] public static partial class Extensions { diff --git a/source/NonGeneric/Extensions.cs b/source/NonGeneric/Extensions.cs index efdc533..4f683f0 100644 --- a/source/NonGeneric/Extensions.cs +++ b/source/NonGeneric/Extensions.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections; -using System.Diagnostics.Contracts; +using System.Collections; namespace Open.Collections.NonGeneric; diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 8c92d65..651869f 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -4,6 +4,7 @@ netstandard2.0;netstandard2.1;net9.0 latest enable + true true true true @@ -18,7 +19,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.1.0 + 4.1.1 MIT true @@ -31,6 +32,7 @@ + diff --git a/source/OrderedDictionary.cs b/source/OrderedDictionary.cs index 6c5ea93..8cfff36 100644 --- a/source/OrderedDictionary.cs +++ b/source/OrderedDictionary.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace Open.Collections; +namespace Open.Collections; /// /// A dictionary that maintains the order of items as they are added @@ -97,7 +90,9 @@ protected override void AddInternal(TKey key, TValue value) /// Adds a node to the lookup dictionary. /// #if NETSTANDARD2_0 +#pragma warning disable IDE0079 // Remove unnecessary suppression [SuppressMessage("Roslynator", "RCS1242:Do not pass non-read-only struct by read-only reference.", Justification = "KeyValuePairs are not truly readonly until NET Standard 2.1.")] +#pragma warning restore IDE0079 // Remove unnecessary suppression #endif protected virtual LinkedListNode> AddNode( in KeyValuePair kvp) diff --git a/source/Queue/Extensions.cs b/source/Queue/Extensions.cs index e863e77..bafa17c 100644 --- a/source/Queue/Extensions.cs +++ b/source/Queue/Extensions.cs @@ -1,6 +1,4 @@ using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections; diff --git a/source/Queue/IQueue.cs b/source/Queue/IQueue.cs index 2b1f57c..bb868dd 100644 --- a/source/Queue/IQueue.cs +++ b/source/Queue/IQueue.cs @@ -1,7 +1,8 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections; +namespace Open.Collections; +/// +/// A queue interface. +/// public interface IQueue { /// diff --git a/source/Queue/Queue.Concurrent.cs b/source/Queue/Queue.Concurrent.cs index 6c51dbc..2af79c0 100644 --- a/source/Queue/Queue.Concurrent.cs +++ b/source/Queue/Queue.Concurrent.cs @@ -1,10 +1,13 @@ using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections; public static partial class Queue { + /// + /// A standard implementation that allows for the interface. + /// + /// This allows for other queues to be used interchangeably. public sealed class Concurrent : ConcurrentQueue, IQueue { #if NETSTANDARD2_0 diff --git a/source/Queue/Queue.Standard.cs b/source/Queue/Queue.Standard.cs index 50a11ac..995aa72 100644 --- a/source/Queue/Queue.Standard.cs +++ b/source/Queue/Queue.Standard.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections; +namespace Open.Collections; /// /// Static collection of queue implementations. diff --git a/source/ReadOnlyCollectionAdapter.cs b/source/ReadOnlyCollectionAdapter.cs index b80964f..dbacc66 100644 --- a/source/ReadOnlyCollectionAdapter.cs +++ b/source/ReadOnlyCollectionAdapter.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; +using System.Collections; namespace Open.Collections; diff --git a/source/ReadOnlyCollectionWrapper.cs b/source/ReadOnlyCollectionWrapper.cs index baffb91..2768c52 100644 --- a/source/ReadOnlyCollectionWrapper.cs +++ b/source/ReadOnlyCollectionWrapper.cs @@ -1,9 +1,5 @@ using Open.Disposable; -using System; using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace Open.Collections; diff --git a/source/Subsets.cs b/source/Subsets.cs index 3a1be16..0ce9b07 100644 --- a/source/Subsets.cs +++ b/source/Subsets.cs @@ -1,9 +1,6 @@ -using System; -using System.Buffers; -using System.Collections.Generic; +using System.Buffers; using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Diagnostics.Contracts; namespace Open.Collections; diff --git a/source/Synchronized/ConcurrentList.cs b/source/Synchronized/ConcurrentList.cs index 33e4520..c027f75 100644 --- a/source/Synchronized/ConcurrentList.cs +++ b/source/Synchronized/ConcurrentList.cs @@ -1,11 +1,5 @@ using Open.Threading; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Threading; namespace Open.Collections.Synchronized; diff --git a/source/Synchronized/ISynchronizedCollection.cs b/source/Synchronized/ISynchronizedCollection.cs index 4804b67..81bb3c9 100644 --- a/source/Synchronized/ISynchronizedCollection.cs +++ b/source/Synchronized/ISynchronizedCollection.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; - -namespace Open.Collections; +namespace Open.Collections; +/// +/// Interface for a collection that can be synchronized (thread-safe). +/// public interface ISynchronizedCollection : ICollection { /// diff --git a/source/Synchronized/ISynchronizedCollectionWrapper.cs b/source/Synchronized/ISynchronizedCollectionWrapper.cs index 71c62b1..e2d6da9 100644 --- a/source/Synchronized/ISynchronizedCollectionWrapper.cs +++ b/source/Synchronized/ISynchronizedCollectionWrapper.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; +/// +/// Interface for a wrapper around a collection that allows for tread-safe modification. +/// public interface ISynchronizedCollectionWrapper : ISynchronizedCollection where TCollection : ICollection diff --git a/source/Synchronized/LockSynchronizedCollectionWrapper.cs b/source/Synchronized/LockSynchronizedCollectionWrapper.cs index 1f938ae..8acaa43 100644 --- a/source/Synchronized/LockSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/LockSynchronizedCollectionWrapper.cs @@ -1,9 +1,5 @@ using Open.Threading; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Open.Collections.Synchronized; @@ -83,7 +79,7 @@ public override bool Remove(T item) lock (Sync) return base.Remove(item); } -#endregion + #endregion /// [ExcludeFromCodeCoverage] diff --git a/source/Synchronized/LockSynchronizedDictionaryWrapper.cs b/source/Synchronized/LockSynchronizedDictionaryWrapper.cs index 7f4d809..e47a2cf 100644 --- a/source/Synchronized/LockSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/LockSynchronizedDictionaryWrapper.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; +/// +/// A Monitor synchronized wrapper for a dictionary. +/// [ExcludeFromCodeCoverage] public class LockSynchronizedDictionaryWrapper(TDictionary dictionary) : LockSynchronizedCollectionWrapper, TDictionary>(dictionary), IDictionary diff --git a/source/Synchronized/LockSynchronizedHashSet.cs b/source/Synchronized/LockSynchronizedHashSet.cs index b1d413b..37ac5ee 100644 --- a/source/Synchronized/LockSynchronizedHashSet.cs +++ b/source/Synchronized/LockSynchronizedHashSet.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; /// -/// A synchronized . +/// A Monitor synchronized . /// public sealed class LockSynchronizedHashSet : LockSynchronizedCollectionWrapper>, ISet { @@ -19,7 +15,7 @@ public LockSynchronizedHashSet() : base([]) { } /// Constructs a new instance with the specified capacity. /// [ExcludeFromCodeCoverage] - public LockSynchronizedHashSet(IEnumerable collection) : base(new HashSet(collection)) { } + public LockSynchronizedHashSet(IEnumerable collection) : base([.. collection]) { } /// /// Constructs a new instance with the specified capacity and comparer. diff --git a/source/Synchronized/LockSynchronizedIndexedDictionary.cs b/source/Synchronized/LockSynchronizedIndexedDictionary.cs index 551f59b..d6c0e1b 100644 --- a/source/Synchronized/LockSynchronizedIndexedDictionary.cs +++ b/source/Synchronized/LockSynchronizedIndexedDictionary.cs @@ -1,7 +1,8 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; +/// +/// A Monitor synchronized . +/// [ExcludeFromCodeCoverage] // Nothing worth covering here yet. public sealed class LockSynchronizedIndexedDictionary(int capacity = 0) : LockSynchronizedDictionaryWrapper>(new IndexedDictionary(capacity)), IIndexedDictionary diff --git a/source/Synchronized/LockSynchronizedLinkedList.cs b/source/Synchronized/LockSynchronizedLinkedList.cs index c5f7d77..a475b70 100644 --- a/source/Synchronized/LockSynchronizedLinkedList.cs +++ b/source/Synchronized/LockSynchronizedLinkedList.cs @@ -1,15 +1,20 @@ using Open.Threading; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections.Synchronized; // LinkedLists are a bit different and don't have an default interface. // Overriding the .Value property of the nodes is beyond the scope of this. All that's needed is to synchronize the collection. -public sealed class LockSynchronizedLinkedList : LockSynchronizedCollectionWrapper>, ILinkedList + +/// +/// A Monitor synchronized wrapper for a . +/// +public sealed class LockSynchronizedLinkedList + : LockSynchronizedCollectionWrapper>, ILinkedList { + /// public LockSynchronizedLinkedList() : base(new LinkedList()) { } + /// [ExcludeFromCodeCoverage] public LockSynchronizedLinkedList(IEnumerable collection) : base(new LinkedList(collection)) { } diff --git a/source/Synchronized/LockSynchronizedList.cs b/source/Synchronized/LockSynchronizedList.cs index 1172a5f..effad64 100644 --- a/source/Synchronized/LockSynchronizedList.cs +++ b/source/Synchronized/LockSynchronizedList.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; /// /// A synchronized list. @@ -23,5 +20,5 @@ public LockSynchronizedList(int capacity = 0) : base(new List(capacity)) { } /// /// Constructs a new instance with the specified collection. /// - public LockSynchronizedList(IEnumerable collection) : base(new List(collection)) { } + public LockSynchronizedList(IEnumerable collection) : base([.. collection]) { } } diff --git a/source/Synchronized/LockSynchronizedListWrapper.cs b/source/Synchronized/LockSynchronizedListWrapper.cs index 33d0d15..e371bbf 100644 --- a/source/Synchronized/LockSynchronizedListWrapper.cs +++ b/source/Synchronized/LockSynchronizedListWrapper.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; +/// +/// A Monitor synchronized list wrapper. +/// [ExcludeFromCodeCoverage] public class LockSynchronizedListWrapper( TList list, bool owner = false) @@ -39,6 +39,7 @@ public void RemoveAt(int index) } } +/// [ExcludeFromCodeCoverage] public class LockSynchronizedListWrapper( IList list, bool owner = false) diff --git a/source/Synchronized/LockSynchronizedOrderedDictionary.cs b/source/Synchronized/LockSynchronizedOrderedDictionary.cs index 668e092..d0f19e9 100644 --- a/source/Synchronized/LockSynchronizedOrderedDictionary.cs +++ b/source/Synchronized/LockSynchronizedOrderedDictionary.cs @@ -1,7 +1,8 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; +/// +/// A synchronized . +/// [ExcludeFromCodeCoverage] public sealed class LockSynchronizedOrderedDictionary( int capacity = 0) diff --git a/source/Synchronized/LockSynchronizedQueue.cs b/source/Synchronized/LockSynchronizedQueue.cs index af6c4eb..423b3ab 100644 --- a/source/Synchronized/LockSynchronizedQueue.cs +++ b/source/Synchronized/LockSynchronizedQueue.cs @@ -1,6 +1,4 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; [ExcludeFromCodeCoverage] public class LockSynchronizedQueue : Queue.Standard, IQueue diff --git a/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs b/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs index 1494430..fe26efc 100644 --- a/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedCollectionWrapper.cs @@ -1,10 +1,5 @@ using Open.Threading; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; namespace Open.Collections.Synchronized; diff --git a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs index da94b82..21e2f6e 100644 --- a/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs @@ -1,8 +1,4 @@ using Open.Threading; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace Open.Collections.Synchronized; diff --git a/source/Synchronized/ReadWriteSynchronizedHashSet.cs b/source/Synchronized/ReadWriteSynchronizedHashSet.cs index 6c60127..494a128 100644 --- a/source/Synchronized/ReadWriteSynchronizedHashSet.cs +++ b/source/Synchronized/ReadWriteSynchronizedHashSet.cs @@ -1,8 +1,4 @@ using Open.Threading; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace Open.Collections.Synchronized; @@ -23,7 +19,7 @@ public ReadWriteSynchronizedHashSet() : base([]) { } /// /// [ExcludeFromCodeCoverage] - public ReadWriteSynchronizedHashSet(IEnumerable collection) : base(new HashSet(collection)) { } + public ReadWriteSynchronizedHashSet(IEnumerable collection) : base([.. collection]) { } /// /// Constructs a new instance with the specified capacity and comparer. @@ -31,7 +27,7 @@ public ReadWriteSynchronizedHashSet(IEnumerable collection) : base(new HashSe [ExcludeFromCodeCoverage] public ReadWriteSynchronizedHashSet(IEnumerable collection, IEqualityComparer comparer) : base(new HashSet(collection, comparer)) { } - // Asumes that .Contains is a thread-safe read-only operation. + // Assumes that .Contains is a thread-safe read-only operation. // But any potentially iterative operation will be locked. /// diff --git a/source/Synchronized/ReadWriteSynchronizedLinkedList.cs b/source/Synchronized/ReadWriteSynchronizedLinkedList.cs index 12c590b..f7f4a9e 100644 --- a/source/Synchronized/ReadWriteSynchronizedLinkedList.cs +++ b/source/Synchronized/ReadWriteSynchronizedLinkedList.cs @@ -1,6 +1,4 @@ using Open.Threading; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections.Synchronized; @@ -16,7 +14,7 @@ public sealed class ReadWriteSynchronizedLinkedList /// [ExcludeFromCodeCoverage] public ReadWriteSynchronizedLinkedList() - : base(new LinkedList()) { } + : base([]) { } /// /// Constructs a new instance with the specified collection. diff --git a/source/Synchronized/ReadWriteSynchronizedList.cs b/source/Synchronized/ReadWriteSynchronizedList.cs index 0c70fc6..5052a60 100644 --- a/source/Synchronized/ReadWriteSynchronizedList.cs +++ b/source/Synchronized/ReadWriteSynchronizedList.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Open.Collections.Synchronized; +namespace Open.Collections.Synchronized; /// /// A synchronized that uses a for thread safety. @@ -26,5 +23,5 @@ public ReadWriteSynchronizedList(int capacity = 0) /// Constructs a new instance with the specified collection. /// public ReadWriteSynchronizedList(IEnumerable collection) - : base(new List(collection)) { } + : base([.. collection]) { } } diff --git a/source/Synchronized/ReadWriteSynchronizedListWrapper.cs b/source/Synchronized/ReadWriteSynchronizedListWrapper.cs index 2ac52ea..0f6aca7 100644 --- a/source/Synchronized/ReadWriteSynchronizedListWrapper.cs +++ b/source/Synchronized/ReadWriteSynchronizedListWrapper.cs @@ -1,6 +1,4 @@ using Open.Threading; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections.Synchronized; diff --git a/source/Synchronized/Readme.md b/source/Synchronized/Readme.md index be8f371..9db7399 100644 --- a/source/Synchronized/Readme.md +++ b/source/Synchronized/Readme.md @@ -19,7 +19,7 @@ A "snapshot" is simply a copy of the collection at a given moment that can then ### Reads Any operation that the collection cannot be changing (adding/removing) while executing. -ie. ```.Contains(item)``` +i.e. ```.Contains(item)``` ### Writes diff --git a/source/Synchronized/TrackedCollectionWrapper.cs b/source/Synchronized/TrackedCollectionWrapper.cs index 75e94ae..e8e766f 100644 --- a/source/Synchronized/TrackedCollectionWrapper.cs +++ b/source/Synchronized/TrackedCollectionWrapper.cs @@ -1,12 +1,6 @@ using Open.Threading; -using System; using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; namespace Open.Collections.Synchronized; diff --git a/source/Synchronized/TrackedDictionaryWrapper.cs b/source/Synchronized/TrackedDictionaryWrapper.cs index c3df836..fbf56da 100644 --- a/source/Synchronized/TrackedDictionaryWrapper.cs +++ b/source/Synchronized/TrackedDictionaryWrapper.cs @@ -1,7 +1,4 @@ using Open.Threading; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections.Synchronized; diff --git a/source/Synchronized/TrackedIndexedDictionaryWrapper.cs b/source/Synchronized/TrackedIndexedDictionaryWrapper.cs index 59bfa5d..ca06c52 100644 --- a/source/Synchronized/TrackedIndexedDictionaryWrapper.cs +++ b/source/Synchronized/TrackedIndexedDictionaryWrapper.cs @@ -1,6 +1,4 @@ using Open.Threading; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections.Synchronized; @@ -110,6 +108,9 @@ public bool SetValueAt(int index, TValue value, out TKey key) return result; } + /// + /// Synchronizes adding an item to the internal collection. + /// protected override int AddSynchronized(TKey key, TValue value) { int index = -1; @@ -134,6 +135,9 @@ protected override int AddSynchronized(TKey key, TValue value) => AddSynchronized(key, value); } +/// +/// A synchronized wrapper for a dictionary that uses a for synchronization. +/// public class TrackedIndexedDictionaryWrapper : TrackedIndexedDictionaryWrapper> where TKey : notnull @@ -153,22 +157,28 @@ public TrackedIndexedDictionaryWrapper(IIndexedDictionary dictiona } } +/// +/// A synchronized dictionary that uses a for synchronization. +/// public sealed class TrackedIndexedDictionary : TrackedIndexedDictionaryWrapper where TKey : notnull { + /// [ExcludeFromCodeCoverage] public TrackedIndexedDictionary(int capacity, ModificationSynchronizer? sync = null) : base(new IndexedDictionary(capacity), sync) { } + /// [ExcludeFromCodeCoverage] public TrackedIndexedDictionary(int capacity, out ModificationSynchronizer sync) : base(new IndexedDictionary(capacity), out sync) { } + /// [ExcludeFromCodeCoverage] public TrackedIndexedDictionary(ModificationSynchronizer? sync) : base(new IndexedDictionary(), sync) @@ -190,9 +200,11 @@ public TrackedIndexedDictionary() : this(null) /// [ExcludeFromCodeCoverage] - public override TKey GetKeyAt(int index) => InternalSource.GetKeyAt(index); + public override TKey GetKeyAt(int index) + => InternalSource.GetKeyAt(index); /// [ExcludeFromCodeCoverage] - public override TValue GetValueAt(int index) => InternalSource.GetValueAt(index); + public override TValue GetValueAt(int index) + => InternalSource.GetValueAt(index); } diff --git a/source/Synchronized/TrackedListWrapper.cs b/source/Synchronized/TrackedListWrapper.cs index eb84b5f..8edf60b 100644 --- a/source/Synchronized/TrackedListWrapper.cs +++ b/source/Synchronized/TrackedListWrapper.cs @@ -1,7 +1,4 @@ using Open.Threading; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Open.Collections.Synchronized; @@ -103,7 +100,7 @@ public T RemoveAt(int index) void IList.RemoveAt(int index) => RemoveAt(index); /// - /// Synchonizes finding an item (), and if found, replaces it with the . + /// Synchronizes finding an item (), and if found, replaces it with the . /// /// If is true and the is not found. public bool Replace(T target, T replacement, bool throwIfNotFound = false) diff --git a/source/_Imports.cs b/source/_Imports.cs new file mode 100644 index 0000000..4002d13 --- /dev/null +++ b/source/_Imports.cs @@ -0,0 +1,4 @@ +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics.Contracts; +global using System.Runtime.CompilerServices; diff --git a/testing/Open.Collections.Tests/PermutorTests.cs b/testing/Open.Collections.Tests/PermutorTests.cs index 6976da4..804aea7 100644 --- a/testing/Open.Collections.Tests/PermutorTests.cs +++ b/testing/Open.Collections.Tests/PermutorTests.cs @@ -1,7 +1,7 @@ -using System; +using FluentAssertions; +using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions; using Xunit; namespace Open.Collections.Tests; @@ -22,7 +22,7 @@ public void TestNoDuplicatePermutations() public void TestSpecificPermutation() { int[] numbers = [1, 2, 3]; - var permutations = numbers.AsMemory().Permutations().Select(m=>m.ToArray()).ToList(); + var permutations = numbers.AsMemory().Permutations().Select(m => m.ToArray()).ToList(); permutations.Count.Should().Be(6); int[] expectedPermutation = [2, 1, 3]; Assert.Contains(expectedPermutation, permutations); @@ -84,7 +84,7 @@ public void TestStableHeapsAlgorithmOrder() { int[] original = [1, 2, 3]; var permutations = new List(); - foreach(var s in original.Permutations()) + foreach (var s in original.Permutations()) permutations.Add(string.Join(",", s.ToArray())); var expectedPermutations = new List @@ -109,7 +109,7 @@ public void TestStableIndexedOrder() { string s = string.Join(",", original.ToArray().AsSpan().Permutation(i).ToArray()); int c = permutations.IndexOf(s); - Assert.True(c==-1, $"{s} already exists in the set at index [{c}] of {permutations.Count}."); + Assert.True(c == -1, $"{s} already exists in the set at index [{c}] of {permutations.Count}."); permutations.Contains(s).Should().BeFalse(s + " "); permutations.Add(s); } From 1e00af6d052b3759d31ff067d6437efb6dfadc11 Mon Sep 17 00:00:00 2001 From: electricessence <5899455+electricessence@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:05:32 -0700 Subject: [PATCH 18/18] Remove unneeded ToImmutableArray extensions. Updated to latest references. --- source/Extensions.cs | 11 +---------- source/Open.Collections.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/source/Extensions.cs b/source/Extensions.cs index b06bb7e..dbe047f 100644 --- a/source/Extensions.cs +++ b/source/Extensions.cs @@ -973,15 +973,6 @@ public static Span CopyToSpan(this IEnumerable source, Span target) return tLen == count ? target : target.Slice(0, count); } - /// - /// Builds an immutable array using the contents of the span. - /// - public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) => [.. span]; - - /// - public static ImmutableArray ToImmutableArray(this Span span) - => [.. span]; - /// /// Builds an immutable array using the contents of the memory. /// @@ -1008,7 +999,7 @@ public static ReadOnlyCollection ToReadOnlyCollection(this Span span) public static ReadOnlyCollection ToReadOnlyCollection(this ReadOnlyMemory memory) => memory.Span.ToReadOnlyCollection(); - /// + /// public static ReadOnlyCollection ToReadOnlyCollection(this Memory memory) => memory.Span.ToReadOnlyCollection(); diff --git a/source/Open.Collections.csproj b/source/Open.Collections.csproj index 651869f..a6235ca 100644 --- a/source/Open.Collections.csproj +++ b/source/Open.Collections.csproj @@ -19,7 +19,7 @@ https://github.com/Open-NET-Libraries/Open.Collections/ https://github.com/Open-NET-Libraries/Open.Collections/ git - 4.1.1 + 4.2.0 MIT true @@ -32,7 +32,7 @@ - + @@ -54,11 +54,11 @@ - + - +