Skip to content

Commit eef313c

Browse files
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<T>`. Added new methods and overloads to `CollectionWrapper<T, TCollection>` for adding multiple items and ranges, including `ReadOnlySpan<T>` 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<T>`. Added helper methods and classes like `KeyValuePair.Create` and `ReadOnlyCollectionAdapter<T>`. Enhanced `ListWrapper<T, TList>` and `TrackedCollectionWrapper<T, TCollection>` 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.
1 parent 7bb9c1e commit eef313c

12 files changed

+164
-13
lines changed

source/CollectionWrapper.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Runtime.CompilerServices;
45

@@ -49,8 +50,13 @@ public virtual void Add(T item)
4950
AddInternal(in item);
5051
}
5152

53+
#if NET9_0_OR_GREATER
54+
/// <inheritdoc cref="IAddMultiple{T}.AddThese(T, T, ReadOnlySpan{T})"/>
55+
public virtual void AddThese(T item1, T item2, params ReadOnlySpan<T> items)
56+
#else
5257
/// <inheritdoc cref="IAddMultiple{T}.AddThese(T, T, T[])"/>
5358
public virtual void AddThese(T item1, T item2, params T[] items)
59+
#endif
5460
{
5561
AssertIsAlive();
5662
AddInternal(in item1);
@@ -67,6 +73,19 @@ public virtual void AddThese(T item1, T item2, params T[] items)
6773
/// <param name="items">The items to add.</param>
6874
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6975
public virtual void AddRange(IEnumerable<T> items)
76+
{
77+
AssertIsAlive();
78+
if (items is null) return;
79+
foreach (var i in items)
80+
AddInternal(in i);
81+
}
82+
83+
/// <inheritdoc cref="AddRange(IEnumerable{T})"/>
84+
#if NET9_0_OR_GREATER
85+
[OverloadResolutionPriority(1)]
86+
#endif
87+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88+
public virtual void AddRange(ReadOnlySpan<T> items)
7089
{
7190
AssertIsAlive();
7291
foreach (var i in items)
@@ -86,5 +105,5 @@ public virtual bool Remove(T item)
86105
/// <inheritdoc />
87106
public override bool IsReadOnly
88107
=> InternalSource.IsReadOnly;
89-
#endregion
108+
#endregion
90109
}

source/DictionaryToHashSetWrapper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55

66
namespace Open.Collections;
77

8+
/// <summary>
9+
/// A wrapper for a <see cref="IDictionary{TKey, TValue}"/> to implement <see cref="ISet{T}"/>.
10+
/// </summary>
811
[method: ExcludeFromCodeCoverage]
912
public class DictionaryToHashSetWrapper<T>(
1013
IDictionary<T, bool> source)
1114
: ISet<T>
1215
{
16+
/// <summary>
17+
/// The internal source dictionary.
18+
/// </summary>
1319
protected readonly IDictionary<T, bool> InternalSource = source;
1420

1521
/// <inheritdoc />

source/DictionaryWrapper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,21 @@ protected override TValue GetValueInternal(TKey key)
3434
protected override void SetValueInternal(TKey key, TValue value)
3535
=> InternalSource[key] = value;
3636

37+
/// <inheritdoc />
3738
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3839
protected override ICollection<TKey> GetKeys()
3940
=> new ReadOnlyCollectionAdapter<TKey>(
4041
ThrowIfDisposed(InternalSource.Keys),
4142
() => InternalSource.Count);
4243

44+
/// <inheritdoc />
4345
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4446
protected override ICollection<TValue> GetValues()
4547
=> new ReadOnlyCollectionAdapter<TValue>(
4648
ThrowIfDisposed(InternalSource.Values),
4749
() => InternalSource.Count);
4850

51+
/// <inheritdoc />
4952
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5053
protected override void AddInternal(TKey key, TValue value)
5154
=> InternalSource.Add(key, value);

source/Extensions.Combinations.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public static IEnumerable<ArraySegment<T>> CombinationsDistinct<T>(this IEnumera
150150
return source.Count == 0 ? Enumerable.Empty<ArraySegment<T>>() : CombinationsCore(source, true, buffer);
151151
}
152152

153+
/// <summary>
154+
/// Enumerates all possible combinations of values.
155+
/// </summary>
153156
[Obsolete("Deprecated in favor of using .Subsets(length) or .Combinations(length) depending on intent.")]
154157
public static IEnumerable<T[]> Combinations<T>(this IEnumerable<T> elements, int length, bool uniqueOnly)
155158
{

source/Extensions.ConcurrentBag.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ namespace Open.Collections;
99

1010
public static partial class Extensions
1111
{
12+
/// <summary>
13+
/// Attempts to take items from the <see cref="ConcurrentBag{T}"/> while the <paramref name="predicate"/> is true.
14+
/// </summary>
1215
public static IEnumerable<T> TryTakeWhile<T>(this ConcurrentBag<T> target, Func<ConcurrentBag<T>, bool> predicate)
1316
{
1417
if (target is null) throw new ArgumentNullException(nameof(target));
1518
Contract.EndContractBlock();
1619

17-
return TryTakeWhileCpre(target, predicate);
20+
return TryTakeWhileCore(target, predicate);
1821

19-
static IEnumerable<T> TryTakeWhileCpre(ConcurrentBag<T> target, Func<ConcurrentBag<T>, bool> predicate)
22+
static IEnumerable<T> TryTakeWhileCore(ConcurrentBag<T> target, Func<ConcurrentBag<T>, bool> predicate)
2023
{
2124
while (!target.IsEmpty && predicate(target) && target.TryTake(out T? value))
2225
{
@@ -25,6 +28,7 @@ static IEnumerable<T> TryTakeWhileCpre(ConcurrentBag<T> target, Func<ConcurrentB
2528
}
2629
}
2730

31+
/// <inheritdoc cref="TryTakeWhile{T}(ConcurrentBag{T}, Func{bool})"/>
2832
public static IEnumerable<T> TryTakeWhile<T>(this ConcurrentBag<T> target, Func<bool> predicate)
2933
{
3034
if (target is null) throw new ArgumentNullException(nameof(target));
@@ -33,13 +37,19 @@ public static IEnumerable<T> TryTakeWhile<T>(this ConcurrentBag<T> target, Func<
3337
return TryTakeWhile(target, _ => predicate());
3438
}
3539

40+
/// <summary>
41+
/// Trims the <see cref="ConcurrentBag{T}"/> to the specified <paramref name="maxSize"/>.
42+
/// </summary>
3643
public static void Trim<T>(this ConcurrentBag<T> target, int maxSize)
3744
{
3845
foreach (T? _ in TryTakeWhile(target, t => t.Count > maxSize))
3946
{
4047
}
4148
}
4249

50+
/// <summary>
51+
/// Trims the <see cref="ConcurrentBag{T}"/> to the specified <paramref name="maxSize"/> and calls the <paramref name="handler"/> for each trimmed item.
52+
/// </summary>
4353
public static Task TrimAsync<T>(this ConcurrentBag<T> target, int maxSize, Action<T> handler)
4454
{
4555
if (target is null) throw new ArgumentNullException(nameof(target));
@@ -52,5 +62,9 @@ public static Task TrimAsync<T>(this ConcurrentBag<T> target, int maxSize, Actio
5262
);
5363
}
5464

55-
public static Task ClearAsync<T>(this ConcurrentBag<T> target, Action<T> handler) => TrimAsync(target, 0, handler);
65+
/// <summary>
66+
/// Clears the <see cref="ConcurrentBag{T}"/> and calls the <paramref name="handler"/> for each item.
67+
/// </summary>
68+
public static Task ClearAsync<T>(this ConcurrentBag<T> target, Action<T> handler)
69+
=> TrimAsync(target, 0, handler);
5670
}

source/Extensions.Generic.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.Contracts;
4+
using System.Runtime.CompilerServices;
45

56
namespace Open.Collections;
67

@@ -57,6 +58,9 @@ public static void AddRange<T>(
5758
}
5859

5960
/// <inheritdoc cref="AddRange{T}(ICollection{T}, IEnumerable{T})"/>
61+
#if NET9_0_OR_GREATER
62+
[OverloadResolutionPriority(1)]
63+
#endif
6064
public static void AddRange<T>(
6165
this ICollection<T> target,
6266
ReadOnlySpan<T> values)

source/IAddMultiple.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.CompilerServices;
24

35
namespace Open.Collections;
6+
7+
/// <summary>
8+
/// Represents a collection that can add multiple items.
9+
/// </summary>
410
public interface IAddMultiple<T>
511
{
12+
/// <summary>
13+
/// Adds all the items in <paramref name="items"/> to this collection.
14+
/// </summary>
15+
/// <param name="items">The items to add.</param>
16+
void AddRange(IEnumerable<T> items);
17+
618
// Note: "AddThese" is the name because Add can have multiple signatures.
719

820
/// <summary>Adds more than one item.</summary>
921
/// <param name="item1">First item to add.</param>
1022
/// <param name="item2">Additional item to add.</param>
1123
/// <param name="items">Extended param items to add.</param>
12-
void AddThese(T item1, T item2, params T[] items);
24+
#if NET9_0_OR_GREATER
25+
void AddThese(T item1, T item2, params System.ReadOnlySpan<T> items);
1326

14-
/// <summary>
15-
/// Adds all the items in <paramref name="items"/> to this collection.
16-
/// </summary>
17-
/// <param name="items">The items to add.</param>
18-
void AddRange(IEnumerable<T> items);
27+
/// <inheritdoc cref="AddRange(IEnumerable{T})"/>/>
28+
[OverloadResolutionPriority(1)]
29+
void AddRange(ReadOnlySpan<T> items);
30+
#else
31+
void AddThese(T item1, T item2, params T[] items);
32+
#endif
1933
}

source/KeyValuePair.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@
44

55
namespace Open.Collections;
66

7+
/// <summary>
8+
/// A static helper class for creating <see cref="KeyValuePair{TKey, TValue}"/> instances.
9+
/// </summary>
710
public static class KeyValuePair
811
{
12+
/// <summary>
13+
/// Creates a new <see cref="KeyValuePair{TKey, TValue}"/>.
14+
/// </summary>
915
[ExcludeFromCodeCoverage]
1016
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1117
public static KeyValuePair<TKey, TValue> Create<TKey, TValue>(TKey key, TValue value) => new(key, value);

source/ListWrapper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Open.Collections;
66

7+
/// <summary>
8+
/// A wrapper for <see cref="IList{T}"/> that allows for easy extension.
9+
/// </summary>
710
public class ListWrapper<T, TList>(
811
TList source, bool owner = false)
912
: CollectionWrapper<T, TList>(source, owner), IList<T>
@@ -33,15 +36,22 @@ public virtual void RemoveAt(int index)
3336
=> InternalSource.RemoveAt(index);
3437
}
3538

39+
/// <summary>
40+
/// A wrapper for <see cref="IList{T}"/> that allows for easy extension.
41+
/// </summary>
3642
[ExcludeFromCodeCoverage]
3743
public class ListWrapper<T>
3844
: ListWrapper<T, IList<T>>
3945
{
46+
/// <summary>
47+
/// Initializes a new instance of the <see cref="ListWrapper{T}"/> class.
48+
/// </summary>
4049
public ListWrapper(IList<T> source, bool owner = false)
4150
: base(source, owner)
4251
{
4352
}
4453

54+
/// <inheritdoc cref="ListWrapper{T}.ListWrapper(IList{T}, bool)"/>
4555
public ListWrapper(int capacity = 0)
4656
: base(new List<T>(capacity))
4757
{

source/ReadOnlyCollectionAdapter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
namespace Open.Collections;
99

10+
/// <summary>
11+
/// A read-only collection adapter that can be used to wrap an existing collection.
12+
/// </summary>
1013
[method: ExcludeFromCodeCoverage]
1114
public sealed class ReadOnlyCollectionAdapter<T>(
1215
IEnumerable<T> source, Func<int> getCount)
@@ -18,10 +21,14 @@ public sealed class ReadOnlyCollectionAdapter<T>(
1821
? item => c.Contains(item)
1922
: item => source.Contains(item);
2023

24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="ReadOnlyCollectionAdapter{T}"/> class.
26+
/// </summary>
2127
[ExcludeFromCodeCoverage]
2228
public ReadOnlyCollectionAdapter(IReadOnlyCollection<T> source)
2329
: this(source, () => source.Count) { }
2430

31+
/// <inheritdoc cref="ReadOnlyCollectionAdapter{T}.ReadOnlyCollectionAdapter(ICollection{T})"/>
2532
[ExcludeFromCodeCoverage]
2633
public ReadOnlyCollectionAdapter(ICollection<T> source)
2734
: this(source, () => source.Count) { }

0 commit comments

Comments
 (0)