diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj
index b29d18c..1be10da 100644
--- a/Recyclable.Collections/Recyclable.Collections.csproj
+++ b/Recyclable.Collections/Recyclable.Collections.csproj
@@ -15,10 +15,17 @@
Recyclable.Collections project is an open source framework for operating dynamic lists at performance close to raw arrays. It aims at providing high performance with minimal memory footprint. It implements IList<T>'s interface and is targeted as direct replacements of List<T>, SortableList<T>, PriorityQueue<T> and similar.
The project & source code is free for educational, open-source & personal use.
+ https://github.com/mlemanczyk/Recyclable.Collections
+ https://github.com/mlemanczyk/Recyclable.Collections
+ Marek Lemańczyk 2023
+ https://github.com/mlemanczyk/Recyclable.Collections
+ /README.md
-
+
+ PreserveNewest
+
diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs
index 76d836b..dc22b6a 100644
--- a/Recyclable.Collections/RecyclableList.cs
+++ b/Recyclable.Collections/RecyclableList.cs
@@ -3,8 +3,6 @@ using System.Collections;
namespace Recyclable.Collections
{
- public delegate void ItemAdded(long index, T item);
-
public class RecyclableList : IDisposable, IList
{
private static readonly IEqualityComparer _comparer = EqualityComparer.Default;
@@ -13,9 +11,20 @@ namespace Recyclable.Collections
private readonly List _arrays = new();
private bool _disposedValue;
private long _capacity;
+ protected uint _isUpdating;
- private ItemAdded? _itemAdded;
- public event ItemAdded ItemAdded { add => _itemAdded += value; remove => _itemAdded -= value; }
+ protected virtual void BlockUpdateEvent()
+ {
+ _isUpdating++;
+ }
+
+ protected virtual void UnblockUpdateEvent()
+ {
+ if (_isUpdating > 0)
+ {
+ _isUpdating--;
+ }
+ }
private static IEnumerable EnumerateElements(List arrays, int chunkSize, long totalCount)
{
@@ -58,9 +67,17 @@ namespace Recyclable.Collections
public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize)
{
_blockSize = blockSize;
- foreach (var item in source)
+ BlockUpdateEvent();
+ try
{
- Add(item);
+ foreach (var item in source)
+ {
+ Add(item);
+ }
+ }
+ finally
+ {
+ UnblockUpdateEvent();
}
}
@@ -71,7 +88,10 @@ namespace Recyclable.Collections
set
{
_arrays[(int)(index / _blockSize)][index % _blockSize] = value;
- _itemAdded?.Invoke(index, value);
+ if (!IsUpdating)
+ {
+ ListUpdated();
+ }
}
}
@@ -84,6 +104,7 @@ namespace Recyclable.Collections
public int Count => LongCount <= int.MaxValue ? (int)LongCount : int.MaxValue;
public long LongCount { get; protected set; }
public bool IsReadOnly { get; } = false;
+ public bool IsUpdating => _isUpdating > 0;
public void Add(T item)
{
@@ -95,15 +116,28 @@ namespace Recyclable.Collections
}
var newIndex = LongCount;
- this[newIndex] = item;
- // Don't merge these 2 lines, otherwise we'll have increased count
- // before the item is actually added.
- LongCount++;
- _itemAdded?.Invoke(newIndex, item);
+ // We don't want this[newIndex] set to raise the update event, because
+ // the count would be wrong. We need to increase it, first.
+ BlockUpdateEvent();
+ try
+ {
+ this[newIndex] = item;
+ LongCount++;
+ }
+ finally
+ {
+ UnblockUpdateEvent();
+ }
+
+ if (!IsUpdating)
+ {
+ ListUpdated();
+ }
}
public void Clear()
{
+ BlockUpdateEvent();
try
{
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
@@ -121,9 +155,16 @@ namespace Recyclable.Collections
}
finally
{
- // Make sure the list is cleared so that we no longer use any of the arrays
- LongCount = 0;
- _arrays.Clear();
+ try
+ {
+ // Make sure the list is cleared so that we no longer use any of the arrays
+ LongCount = 0;
+ _arrays.Clear();
+ }
+ finally
+ {
+ UnblockUpdateEvent();
+ }
}
}
@@ -203,6 +244,10 @@ namespace Recyclable.Collections
}
}
+ protected virtual void ListUpdated()
+ {
+ }
+
// override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~RecyclableList()
// {
@@ -216,5 +261,19 @@ namespace Recyclable.Collections
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
+
+ public void BeginUpdate() => _isUpdating++;
+
+ public void EndUpdate()
+ {
+ if (_isUpdating > 0)
+ {
+ _isUpdating--;
+ if (_isUpdating == 0)
+ {
+ ListUpdated();
+ }
+ }
+ }
}
}
diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs
new file mode 100644
index 0000000..78f3382
--- /dev/null
+++ b/Recyclable.Collections/RecyclableSortedList.cs
@@ -0,0 +1,43 @@
+namespace Recyclable.Collections
+{
+ public class RecyclableSortedList : RecyclableList<(TKey Key, TValue Value)>
+ where TKey : notnull
+ {
+ private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer();
+ private readonly SystemRandomNumberGenerator _randomNumberGenerator = new SystemRandomNumberGenerator();
+
+ protected override void ListUpdated()
+ {
+ base.ListUpdated();
+ QuickSort();
+ }
+
+ public RecyclableSortedList() : base()
+ {
+ }
+
+ public RecyclableSortedList(Dictionary source, int blockSize = RecyclableDefaults.BlockSize)
+ : this(source.Select(x => (x.Key, x.Value)), blockSize)
+ {
+ }
+
+ public RecyclableSortedList(IEnumerable<(TKey Key, TValue Value)> source, int blockSize = RecyclableDefaults.BlockSize)
+ : base(source, blockSize)
+ {
+ QuickSort();
+ }
+
+ public void QuickSort()
+ {
+ _isUpdating++;
+ try
+ {
+ this.QuickSort(_comparer, _randomNumberGenerator);
+ }
+ finally
+ {
+ _isUpdating--;
+ }
+ }
+ }
+}
diff --git a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj
index df55634..12982dd 100644
--- a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj
+++ b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
@@ -8,10 +8,6 @@
false
-
-
-
-
diff --git a/Recyclable.CollectionsTests/RecyclableSortedListTests.cs b/Recyclable.CollectionsTests/RecyclableSortedListTests.cs
new file mode 100644
index 0000000..0096f7f
--- /dev/null
+++ b/Recyclable.CollectionsTests/RecyclableSortedListTests.cs
@@ -0,0 +1,88 @@
+using FluentAssertions;
+using Recyclable.Collections;
+
+namespace Recyclable.CollectionsTests
+{
+ [TestClass()]
+ public class RecyclableSortedListTests
+ {
+ private static readonly (int, string)[] _testData = new[] { (5, "a"), (4, "d"), (3, "c"), (2, "b"), (1, "a") };
+
+ [TestMethod]
+ public void ShouldBeSortedWhenCreated()
+ {
+ // Act
+ using var sortedList = new RecyclableSortedList(_testData);
+
+ // Validate
+ _ = sortedList.Should().HaveCount(_testData.Length)
+ .And.BeEquivalentTo(_testData)
+ .And.BeInAscendingOrder((item) => item.Key);
+ }
+
+ [TestMethod]
+ public void ShouldBeEmptyWhenNotInitialized()
+ {
+ // Act
+ using var sortedList = new RecyclableSortedList();
+
+ // Validate
+ _ = sortedList.Should().BeEmpty();
+ }
+
+ [TestMethod]
+ public void ShouldBeSortedWhenInitialized()
+ {
+ // Act
+ using var sortedList = new RecyclableSortedList()
+ {
+ _testData[0],
+ _testData[1],
+ _testData[2],
+ _testData[3],
+ _testData[4]
+ };
+
+ // Validate
+ _ = sortedList.Should().NotBeEmpty()
+ .And.BeInAscendingOrder(x => x.Key)
+ .And.BeEquivalentTo(_testData);
+ }
+
+ [TestMethod]
+ public void ShouldBeSortedWhenUpdateEnds()
+ {
+ // Prepare
+ using var sortedList = new RecyclableSortedList();
+
+ // Act
+ sortedList.BeginUpdate();
+ foreach (var testItem in _testData)
+ {
+ sortedList.Add(testItem);
+ }
+
+ // Validate
+ _ = sortedList.Should().BeInDescendingOrder(x => x.Key);
+
+ // Act
+ sortedList.EndUpdate();
+
+ // Validate
+ _ = sortedList.Should().BeInAscendingOrder(x => x.Key);
+ }
+
+ [TestMethod]
+ public void ShouldBeEmptyAfterClear()
+ {
+ // Prepare
+ using var sortedList = new RecyclableSortedList(_testData);
+
+ // Act
+ sortedList.Clear();
+
+ // Validate
+ _ = sortedList.Should().BeEmpty();
+ }
+ }
+}
\ No newline at end of file