IReadOnlyList<T> & IList interfaces implemented in RecyclableLongList.

This commit is contained in:
mlemanczyk committed 2023-06-16 19:21:31 +02:00
1 parent 9e07e0181c
commit bcab0bbfe2
3 files changed
+159 -28

No files matched your search

+14 -13
View File
@@ -1,5 +1,5 @@
# Recyclable.Collections
`Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList<T>`'s interface and is targeted as direct replacements of `List<T>`, `SortableList<T>`, `PriorityQueue<T>` & similar.
`Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList<T>` interface and is targeted as direct replacements of `List<T>`, `SortableList<T>`, `PriorityQueue<T>` & similar.
## Included
* `RecyclableList<T>`
@@ -78,14 +78,14 @@
1. ✅ Add support for `ReadOnlySpan<T>`
1. ✅ Release 0.0.3-alpha
1. 👉 Implement `List<T>` interfaces
1. 🅿️ `ICollection<T>`
1. 🅿️ `IEnumerable<T>`
1. 🅿️ `IEnumerable`
1. 🅿️ `IList<T>`
1. ✅ `IList<T>`
1. ✅ `ICollection<T>`
1. ✅ `IEnumerable<T>`
1. ✅ `IEnumerable`
1. 👉 `IReadOnlyList<T>`
1. 🅿️ `IReadOnlyCollection<T>`
1. 🅿️ `IReadOnlyList<T>`
1. 🅿️ `ICollection`
1. 🅿️ `IList`
1. 🅿️ `ICollection`
1. 🅿️ Implement list versioning to allow data change identification
1. 🅿️ Make sure that `NeedsClearing` is used & items are cleared in
1. 🅿️ `Clear`
@@ -106,6 +106,7 @@
1. 🅿️ Replace `blockSize` sums by powers of 2, minus 1
1. 🅿️ Remove type castings, if possible
1. 🅿️ Convert generic methods to non-generic
1. 🅿️ Replace integer comparisons with comparisons to 0 / 1, if possible
1. 🅿️ Overflow review
1. 🅿️ Add type casting to `long` for `<<` & `>>` operations, where required
1. 🅿️ Make type castings `checked`
@@ -148,6 +149,10 @@
1. 🅿️ `SpinLockSlimmer`
1. 🅿️ Multi-threading benchmarks
1. 🅿️ Release 0.0.9-beta
1. 🅿️ Extend unit tests
1. 🅿️ `.Add` / `.AddRange` must allow `null` values
1. 🅿️ `.Remove` / `.RemoveAt` / `.Clear` must clear reference when reference type
1. 🅿️ `RecyclableLongListExtensions.CopyTo`
1. 🅿️ Cleanup
1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field
1. 🅿️ Cleanup `RecyclableLongListExtensions`
@@ -157,6 +162,7 @@
1. 🅿️ `ListExtensions`
1. 🅿️ `MathUtils`
1. 🅿️ `SystemRandomNumberGenerator`
1. 🅿️ `RecyclableLongListExtensions`
1. 🅿️ Review and remove warnings & hints
1. 🅿️ Warnings
1. 🅿️ Hints
@@ -166,6 +172,7 @@
1. 🅿️ Release 1.0.0
1. 🅿️ Optimize
1. 🅿️ `RecyclableLongList<T>.Resize`
1. 🅿️ `RecyclableLongList<T>.CopyTo`
1. 🅿️ Check if we can benefit from Sse2 in `.IndexOf`/`.Contains` methods as given in [MS blog](https://devblogs.microsoft.com/dotnet/hardware-intrinsics-in-net-core/).
1. 🅿️ Add support for `ICollection<T>` interface in `.AddRange` & `constructor`
1. 🅿️ Release 2.0.0
@@ -425,9 +432,3 @@ public void RecyclableListExample()
list.Clear();
}
```
## `RecyclableList<T>`
## `RecyclableList<T>`
## `RecyclableList<T>`
+121 -5
View File
@@ -9,12 +9,15 @@ using Recyclable.Collections.Pools;
namespace Recyclable.Collections
{
public partial class RecyclableLongList<T> : IDisposable, IList<T>
public partial class RecyclableLongList<T> : IDisposable, IList<T>, IReadOnlyList<T>, IList
{
private static readonly ArrayPool<T[]> _defaultMemoryBlocksPool = ArrayPool<T[]>.Create();
private static readonly ArrayPool<T> _defaultBlockArrayPool = ArrayPool<T>.Create();
private static readonly T[][] _emptyMemoryBlocksArray = new T[0][];
private static readonly T[] _emptyBlockArray = new T[0];
private static readonly bool _defaultIsNull = default(T) == null;
private static void ThrowIndexOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message);
private static void ThrowArgumentOutOfRange(in string argumentName, in string message) => throw new ArgumentOutOfRangeException(argumentName, message);
internal int _blockSize;
internal byte _blockSizePow2BitShift;
@@ -102,8 +105,6 @@ namespace Recyclable.Collections
}
}
private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message);
protected void AddRangeEnumerated(IEnumerable<T> source, int growByCount)
{
long i, copied = _longCount;
@@ -1223,7 +1224,7 @@ namespace Recyclable.Collections
{
if (index >= _longCount || index < 0)
{
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
ThrowIndexOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
}
_longCount--;
@@ -1262,7 +1263,7 @@ namespace Recyclable.Collections
{
if (index >= _longCount || index < 0)
{
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
ThrowIndexOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
}
_longCount--;
@@ -1317,5 +1318,120 @@ namespace Recyclable.Collections
GC.SuppressFinalize(this);
}
}
#region IList implementation
public bool IsFixedSize => false;
public bool IsSynchronized => false;
private object? _syncRoot;
public object SyncRoot
{
get
{
if (_syncRoot == null)
{
_ = Interlocked.CompareExchange(ref _syncRoot, new object(), null);
}
return _syncRoot;
}
}
object? IList.this[int index]
{
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
set
{
if (value is T typedValue)
{
new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = typedValue;
}
else if (value == null && _defaultIsNull)
{
#nullable disable
new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = default;
#nullable restore
}
else
{
ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
}
}
}
public int Add(object? value)
{
if (value is T typeValue)
{
Add(typeValue);
return (int)(_longCount - 1);
}
else if (value == null && _defaultIsNull)
{
Add(null);
return (int)(_longCount - 1);
}
else
{
ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. { (value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.") }");
return RecyclableDefaults.ItemNotFoundIndex;
}
}
public bool Contains(object? value) => value is T typeValue
? Contains(typeValue)
#nullable disable
: value == null && _defaultIsNull && Contains(default);
#nullable restore
public int IndexOf(object? value) => value is T typeValue
? IndexOf(typeValue)
: value == null && _defaultIsNull
#nullable disable
? IndexOf(default)
#nullable restore
: RecyclableDefaults.ItemNotFoundIndex;
public void Insert(int index, object? value)
{
if (value is T typeValue)
{
Insert(index, typeValue);
}
else if (value == null && _defaultIsNull)
{
#nullable disable
Insert(index, default);
#nullable restore
}
else
{
ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
}
}
public void Remove(object? value)
{
if (value is T typeValue)
{
_ = Remove(typeValue);
}
else if (value == null && _defaultIsNull)
{
#nullable disable
_ = Remove(default);
#nullable restore
}
else
{
ThrowArgumentOutOfRange(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
}
}
public void CopyTo(Array array, int index) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, index);
#endregion
}
}
@@ -6,16 +6,6 @@ namespace Recyclable.Collections
internal static class RecyclableLongListExtensions
{
private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength;
private const long ItemNotFoundIndexLong = -1L;
private const int ItemNotFoundIndex = -1;
public static void CopyItems<T>(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1)
{
for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++)
{
destArray[destItemIndex] = memory[sourceItemIndex];
}
}
public static void CopyTo<T>(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex)
{
@@ -45,6 +35,30 @@ namespace Recyclable.Collections
}
}
public static void CopyTo<T>(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, Array destinationArray, int destinationArrayIndex)
{
if (itemsCount <= 0)
{
return;
}
int startingBlockIndex = (int)(startingIndex / blockSize);
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1;
var destinationIndex = destinationArrayIndex;
Span<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1);
for (int memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++)
{
Array.ConstrainedCopy(sourceMemoryBlocksSpan[memoryBlockIndex], 0, destinationArray, destinationIndex, blockSize);
destinationIndex += blockSize;
}
if (itemsCount - destinationIndex > 0)
{
Array.ConstrainedCopy(sourceMemoryBlocksSpan[lastBlockIndex], 0, destinationArray, destinationIndex, (int)(itemsCount - destinationIndex));
}
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static bool Contains<T>(this RecyclableLongList<T[]> arrays, T item) => arrays.Any(x => x.Contains(item));