diff --git a/README.md b/README.md index 22f280a..5fc4ef5 100644 --- a/README.md +++ b/README.md @@ -126,18 +126,19 @@ 1. ✅ `.Remove` / `.RemoveAt` / `.Clear` / `.Dispose` must clear reference when reference type 1. ✅ `.Clear` / `.Dispose` must return arrays to pools 1. ✅ `int` / `long` variants of `.Count` / `.LongCount`, `.IndexOf` / `.LongIndexOf`, `.Insert`, `.RemoveAt`, `this[]` - 1. 👉 Add `.ToRecyclableList` / `.ToRecyclableLongList` variants for all supported collection types - 1. 🅿️ `RecyclableList` - 1. 🅿️ `RecyclableLongList` - 1. 🅿️ `IList` - 1. 🅿️ `ICollection` - 1. 🅿️ `IEnumerable` - 1. 🅿️ `IEnumerable` - 1. 🅿️ `IReadOnlyList` - 1. 🅿️ `IReadOnlyCollection` - 1. 🅿️ `IList` - 1. 🅿️ `ICollection` - 1. 🅿️ Release 0.0.5 + 1. ✅ Add `.ToRecyclableList` / `.ToRecyclableLongList` variants for all supported collection types + 1. ✅ `Array` + 1. ✅ `ICollection` + 1. ✅ `ICollection` + 1. ✅ `IEnumerable` + 1. ✅ `IEnumerable` + 1. ✅ `IReadOnlyList` + 1. ✅ `ReadOnlySpan` + 1. ✅ `RecyclableList` + 1. ✅ `RecyclableLongList` + 1. ✅ `Span` + 1. ✅ `T[]` + 1. 👉 Release 0.0.5 1. 🅿️ Add support for `ulong` indexing 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths 1. 🅿️ Convert block indexes from `int` to `ulong` or `long` diff --git a/Recyclable.Collections/RecyclableList.Extensions.cs b/Recyclable.Collections/RecyclableList.Extensions.cs index 494e941..d10ac29 100644 --- a/Recyclable.Collections/RecyclableList.Extensions.cs +++ b/Recyclable.Collections/RecyclableList.Extensions.cs @@ -1,19 +1,44 @@ -using System.Runtime.CompilerServices; +using System.Collections; +using System.Runtime.CompilerServices; namespace Recyclable.Collections { public static class RecyclableListExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this Array values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this ICollection values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this ICollection values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IEnumerable values) => new(values); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableList ToRecyclableList(this IEnumerable values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this T[] values) => new(values); + public static RecyclableList ToRecyclableList(this IReadOnlyList values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableList ToRecyclableList(this List values) => new(values); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this ReadOnlySpan values) => new(values); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableList ToRecyclableList(this RecyclableList values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this RecyclableLongList values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this Span values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this T[] values) => new(values); } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c419a2e..ba6bad6 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -19,13 +19,13 @@ namespace Recyclable.Collections internal ulong _version; public ulong Version => _version; - private IEnumerator AddRangeEnumerated(IEnumerable source, int growByCount) + private void AddRangeEnumerated(IEnumerable items, int growByCount) { int targetItemIndex = _count; Span memorySpan; int i; - var enumerator = source.GetEnumerator(); + var enumerator = items.GetEnumerator(); int capacity = _capacity; memorySpan = new(_memoryBlock); @@ -55,15 +55,14 @@ namespace Recyclable.Collections _capacity = capacity; _count = targetItemIndex; - return enumerator; } - private void AddRangeWithKnownCount(IEnumerable source, int currentItemsCount, int requiredAdditionalCapacity) + private void AddRangeWithKnownCount(IEnumerable items, int currentItemsCount, int requiredAdditionalCapacity) { _capacity = RecyclableListHelpers.ResizeAndCopy(this, currentItemsCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)(currentItemsCount + requiredAdditionalCapacity)))); Span memorySpan = new(_memoryBlock); - foreach (var item in source) + foreach (var item in items) { memorySpan[currentItemsCount++] = item; } @@ -93,6 +92,30 @@ namespace Recyclable.Collections } } + public void AddRange(IReadOnlyList items) + { + int sourceItemsCount = items.Count; + if (_capacity < _count + sourceItemsCount) + { + _capacity = RecyclableListHelpers.ResizeAndCopy(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + sourceItemsCount)))); + } + + Span memorySpan = new(_memoryBlock, _count, sourceItemsCount); + for (var sourceItemIndex = 0; sourceItemIndex < sourceItemsCount; sourceItemIndex++) + { + memorySpan[sourceItemIndex] = items[sourceItemIndex]; + } + + _count += sourceItemsCount; + } + + public RecyclableList(in Array source) + { + _capacity = RecyclableDefaults.InitialCapacity; + _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + AddRange(source); + } + public RecyclableList(RecyclableList source) { _capacity = RecyclableDefaults.InitialCapacity; @@ -121,6 +144,13 @@ namespace Recyclable.Collections AddRange(source); } + public RecyclableList(IReadOnlyList source) + { + _capacity = RecyclableDefaults.InitialCapacity; + _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + AddRange(source); + } + public RecyclableList(List source) { _capacity = RecyclableDefaults.InitialCapacity; @@ -128,13 +158,38 @@ namespace Recyclable.Collections AddRange(source); } - public RecyclableList(IList source) + public RecyclableList(ICollection source) { _capacity = RecyclableDefaults.InitialCapacity; _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; AddRange(source); } + public RecyclableList(ICollection source) + { + _capacity = RecyclableDefaults.InitialCapacity; + _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + AddRange(source); + } + + public RecyclableList(IEnumerable source, int initialCapacity = RecyclableDefaults.InitialCapacity) + { + if (initialCapacity >= RecyclableDefaults.InitialCapacity) + { + _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) + : new T[initialCapacity]; + _capacity = _memoryBlock.Length; + } + else + { + _capacity = RecyclableDefaults.InitialCapacity; + _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + } + + AddRange(source, RecyclableDefaults.MinPooledArrayLength); + } + public RecyclableList(IEnumerable source, int initialCapacity = RecyclableDefaults.InitialCapacity) { if (initialCapacity >= RecyclableDefaults.InitialCapacity) @@ -153,6 +208,13 @@ namespace Recyclable.Collections AddRange(source); } + public RecyclableList(Span source) + { + _capacity = RecyclableDefaults.InitialCapacity; + _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + AddRange(source); + } + public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -197,6 +259,19 @@ namespace Recyclable.Collections _version++; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(in Array items) + { + if (_capacity < _count + items.Length) + { + _capacity = RecyclableListHelpers.ResizeAndCopy(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Length)))); + } + + Array.Copy(items, items.GetLowerBound(0), _memoryBlock, _count, items.LongLength); + _count += items.Length; + _version++; + } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(in T[] items) { @@ -223,6 +298,19 @@ namespace Recyclable.Collections _version++; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(Span items) + { + if (_capacity < _count + items.Length) + { + _capacity = RecyclableListHelpers.ResizeAndCopy(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Length)))); + } + + items.CopyTo(new Span(_memoryBlock, _count, items.Length)); + _count += items.Length; + _version++; + } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(List items) { @@ -237,7 +325,20 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public void AddRange(IList items) + public void AddRange(ICollection items) + { + if (_capacity < _count + items.Count) + { + _capacity = RecyclableListHelpers.ResizeAndCopy(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Count)))); + } + + items.CopyTo(_memoryBlock, _count); + _count += items.Count; + _version++; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(ICollection items) { if (_capacity < _count + items.Count) { @@ -313,24 +414,79 @@ namespace Recyclable.Collections _version++; } + public IEnumerator AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + int targetItemIndex = _count; + Span memorySpan; + + int i; + var enumerator = source.GetEnumerator(); + + int capacity = _capacity; + memorySpan = new(_memoryBlock); + if (enumerator.MoveNext()) + { + int available = capacity - targetItemIndex; + do + { + if (targetItemIndex + growByCount > capacity) + { + capacity = RecyclableListHelpers.ResizeAndCopy(this, targetItemIndex, checked((int)BitOperations.RoundUpToPowerOf2((uint)(targetItemIndex + growByCount)))); + memorySpan = new(_memoryBlock); + available = capacity - targetItemIndex; + } + + for (i = 0; i < available; i++) + { + memorySpan[targetItemIndex++] = (T)enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + } + while (i >= available); + } + + _capacity = capacity; + _count = targetItemIndex; + return enumerator; + } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(IEnumerable items, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - if (items is T[] sourceArray) + if (items is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + } + else if (items is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + } + else if (items is T[] sourceArray) { AddRange(sourceArray); } + else if (items is Array sourceArrayWithObjects) + { + AddRange(sourceArrayWithObjects); + } else if (items is List sourceList) { AddRange(sourceList); } - else if (items is RecyclableList sourceRecyclableList) + else if (items is ICollection sourceICollection) { - AddRange(sourceRecyclableList); + AddRange(sourceICollection); } - else if (items is IList sourceIList) + else if (items is ICollection sourceICollectionWithObjects) { - AddRange(sourceIList); + AddRange(sourceICollectionWithObjects); + } + else if (items is IReadOnlyList sourceIReadOnlyList) + { + AddRange(sourceIReadOnlyList); } else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity) && requiredAdditionalCapacity != 0) { diff --git a/Recyclable.Collections/RecyclableLongList.Extensions.cs b/Recyclable.Collections/RecyclableLongList.Extensions.cs index 8dd6896..5f24356 100644 --- a/Recyclable.Collections/RecyclableLongList.Extensions.cs +++ b/Recyclable.Collections/RecyclableLongList.Extensions.cs @@ -1,19 +1,44 @@ -using System.Runtime.CompilerServices; +using System.Collections; +using System.Runtime.CompilerServices; namespace Recyclable.Collections { public static class RecyclableLongListExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableLongList ToRecyclableLongList(this Array values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static RecyclableLongList ToRecyclableLongList(this Array values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableLongList ToRecyclableLongList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static RecyclableLongList ToRecyclableLongList(this ICollection values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableLongList ToRecyclableLongList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static RecyclableLongList ToRecyclableLongList(this ICollection values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableLongList ToRecyclableLongList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this IReadOnlyList values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this List values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this ReadOnlySpan values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this RecyclableList values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this RecyclableLongList values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this Span values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this T[] values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); } } diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 6f54dda..c396217 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -60,12 +60,12 @@ namespace Recyclable.Collections public int ReservedBlockCount => _reservedBlockCount; public ulong Version => _version; - private void AddRangeEnumerated(IEnumerable source) + private void AddRangeEnumerated(IEnumerable items) { long i, copied = _longCount; int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; - using IEnumerator enumerator = source.GetEnumerator(); + using IEnumerator enumerator = items.GetEnumerator(); if (!enumerator.MoveNext()) { return; @@ -134,7 +134,7 @@ namespace Recyclable.Collections } } - private void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + private void AddRangeWithKnownCount(IEnumerable items, int requiredAdditionalCapacity) { long copied = _longCount; int blockSize = _blockSize; @@ -158,7 +158,7 @@ namespace Recyclable.Collections var memoryBlocksCount = _reservedBlockCount; memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - foreach (var item in source) + foreach (var item in items) { blockArraySpan[targetItemIdx++] = item; if (targetItemIdx == blockSize) @@ -199,139 +199,126 @@ namespace Recyclable.Collections } } - public RecyclableLongList(in T[] source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(in T[] source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - if (expectedItemsCount > 0) - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); - _blockSizeMinus1 = minBlockSize - 1; - } - else - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _blockSizeMinus1 = _blockSize - 1; - _memoryBlocks = _emptyMemoryBlocksArray; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.LongLength))); + _blockSizeMinus1 = minBlockSize - 1; AddRange(source); } - public RecyclableLongList(in Array source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(in Array source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - if (expectedItemsCount > 0) - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); - _blockSizeMinus1 = minBlockSize - 1; - } - else - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _blockSizeMinus1 = _blockSize - 1; - _memoryBlocks = _emptyMemoryBlocksArray; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.LongLength))); + _blockSizeMinus1 = minBlockSize - 1; AddRange(source); } - public RecyclableLongList(ReadOnlySpan source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(IReadOnlyList source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - if (expectedItemsCount > 0) - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); - _blockSizeMinus1 = minBlockSize - 1; - } - else - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _blockSizeMinus1 = _blockSize - 1; - _memoryBlocks = _emptyMemoryBlocksArray; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, BitOperations.RoundUpToPowerOf2((uint)source.Count)); + _blockSizeMinus1 = minBlockSize - 1; AddRange(source); } - public RecyclableLongList(RecyclableList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(ReadOnlySpan source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - if (expectedItemsCount > 0) - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); - _blockSizeMinus1 = minBlockSize - 1; - } - else - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _blockSizeMinus1 = _blockSize - 1; - _memoryBlocks = _emptyMemoryBlocksArray; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Length))); + _blockSizeMinus1 = minBlockSize - 1; AddRange(source); } - public RecyclableLongList(RecyclableLongList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(RecyclableList source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - if (expectedItemsCount > 0) - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); - _blockSizeMinus1 = minBlockSize - 1; - } - else - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _blockSizeMinus1 = _blockSize - 1; - _memoryBlocks = _emptyMemoryBlocksArray; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, BitOperations.RoundUpToPowerOf2((uint)source.Count)); + _blockSizeMinus1 = minBlockSize - 1; AddRange(source); } - public RecyclableLongList(List source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(RecyclableLongList source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - if (expectedItemsCount > 0) - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); - _blockSizeMinus1 = minBlockSize - 1; - } - else - { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _blockSizeMinus1 = _blockSize - 1; - _memoryBlocks = _emptyMemoryBlocksArray; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Count))); + _blockSizeMinus1 = minBlockSize - 1; AddRange(source); } - public RecyclableLongList(IList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableLongList(Span source, int minBlockSize = RecyclableDefaults.BlockSize) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)source.Length))); + _blockSizeMinus1 = minBlockSize - 1; + + AddRange(source); + } + + public RecyclableLongList(List source, int minBlockSize = RecyclableDefaults.BlockSize) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, BitOperations.RoundUpToPowerOf2((uint)source.Count)); + _blockSizeMinus1 = minBlockSize - 1; + + AddRange(source); + } + + public RecyclableLongList(ICollection source, int minBlockSize = RecyclableDefaults.BlockSize) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, BitOperations.RoundUpToPowerOf2((uint)source.Count)); + _blockSizeMinus1 = minBlockSize - 1; + + AddRange(source); + } + + public RecyclableLongList(ICollection source, int minBlockSize = RecyclableDefaults.BlockSize) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, BitOperations.RoundUpToPowerOf2((uint)source.Count)); + _blockSizeMinus1 = minBlockSize - 1; + + AddRange(source); + } + + public RecyclableLongList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + { + Helpers.SetupBlockArrayPooling(this, minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize))); if (expectedItemsCount > 0) { - Helpers.SetupBlockArrayPooling(this, minBlockSize); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((int)BitOperations.RoundUpToPowerOf2((uint)expectedItemsCount.Value))); _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -423,7 +410,7 @@ namespace Recyclable.Collections targetBlockIndex = _nextItemBlockIndex; long fullBlockItemsCount = items.LongLength - blockSize, - sourceItemIndex = blockSize - _nextItemIndex; + sourceItemIndex = Math.Min(blockSize - _nextItemIndex, items.LongLength); Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); @@ -439,11 +426,25 @@ namespace Recyclable.Collections if ((blockSize = (int)(items.LongLength - sourceItemIndex)) > 0) { Array.Copy(items, sourceItemIndex, memoryBlocksSpan[targetBlockIndex], 0, blockSize); + _lastBlockWithData = targetBlockIndex; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = blockSize; + } + else + { + _lastBlockWithData = targetBlockIndex - 1; + if (_nextItemIndex + sourceItemIndex >= _blockSize) + { + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += (int)sourceItemIndex; + } } - _nextItemIndex = blockSize; _longCount = targetCapacity; - _lastBlockWithData = targetBlockIndex; _version++; } @@ -690,6 +691,62 @@ namespace Recyclable.Collections _version++; } + public void AddRange(Span items) + { + if (items.Length == 0) + { + return; + } + + long targetCapacity = _longCount + items.Length; + if (_capacity < targetCapacity) + { + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (items.Length >= targetBlockArraySpan.Length) + { + items[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + items = items[targetBlockArraySpan.Length..]; + if (items.Length == 0) + { + break; + } + + targetBlockIndex++; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (items.Length > 0) + { + items.CopyTo(targetBlockArraySpan); + if (_nextItemBlockIndex != targetBlockIndex) + { + _nextItemIndex = items.Length; + _nextItemBlockIndex = targetBlockIndex; + } + else + { + _nextItemIndex += items.Length; + } + } + else + { + _nextItemBlockIndex = targetBlockIndex + 1; + _nextItemIndex = 0; + } + + _longCount = targetCapacity; + _lastBlockWithData = targetBlockIndex; + _version++; + } + public void AddRange(List items) { if (items.Count == 0) @@ -738,7 +795,7 @@ namespace Recyclable.Collections _version++; } - public void AddRange(IList items) + public void AddRange(ICollection items) { if (items.Count == 0) { @@ -811,27 +868,186 @@ namespace Recyclable.Collections } } + public void AddRange(ICollection items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared((int)BitOperations.RoundUpToPowerOf2((uint)items.Count)) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + if (_nextItemBlockIndex != targetBlockIndex) + { + _nextItemIndex = itemsSpan.Length; + _nextItemBlockIndex = targetBlockIndex; + } + else + { + _nextItemIndex += itemsSpan.Length; + } + } + else + { + _nextItemIndex = 0; + } + + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(itemsBuffer, _needsClearing); + } + + _version++; + } + } + + public void AddRange(IEnumerable source) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + blockSize > capacity) + { + capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(capacity + blockSize)))); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = (T)enumerator.Current; + + if (!enumerator.MoveNext()) + { + _lastBlockWithData = targetBlockIdx; + _longCount += copied + i - _longCount; + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _capacity = capacity; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + public void AddRange(IEnumerable items) { - if (items is RecyclableLongList sourceRecyclableLongList) - { - AddRange(sourceRecyclableLongList); - } - else if (items is RecyclableList sourceRecyclableList) + if (items is RecyclableList sourceRecyclableList) { AddRange(sourceRecyclableList); } + else if (items is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + } else if (items is T[] sourceArray) { AddRange(sourceArray); } + else if (items is Array sourceArrayWithObjects) + { + AddRange(sourceArrayWithObjects); + } else if (items is List sourceList) { AddRange(sourceList); } - else if (items is IList sourceIList) + else if (items is ICollection sourceICollection) { - AddRange(sourceIList); + AddRange(sourceICollection); + } + else if (items is ICollection sourceICollectionWithObjects) + { + AddRange(sourceICollectionWithObjects); + } + else if (items is IReadOnlyList sourceIReadOnlyList) + { + AddRange(sourceIReadOnlyList); } else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { @@ -845,6 +1061,52 @@ namespace Recyclable.Collections } } + public void AddRange(IReadOnlyList items) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long sourceItemsCount = items.Count; + long requiredCapacity = copied + sourceItemsCount; + if (requiredCapacity == 0) + { + return; + } + + if (_capacity < requiredCapacity) + { + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity))); + blockSize = _blockSize; + } + + var memoryBlocksCount = _reservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for(var itemIndex = 0; itemIndex < sourceItemsCount; itemIndex++) + { + blockArraySpan[targetItemIdx++] = items[itemIndex]; + if (targetItemIdx == blockSize) + { + targetItemIdx = 0; + if (++targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + _lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1); + } + public void Clear() { if (_longCount == 0) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 0fada0a..c75652c 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Recyclable.Collections; using Recyclable.Collections.Linq; +using System.Collections; #pragma warning disable xUnit1026, RCS1163, IDE0060, RCS1235 @@ -29,10 +30,34 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableList(); // Act - if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) { list.AddRange(new ReadOnlySpan((long[])testData)); } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange(new Span((long[])testData)); + } else if (testData is long[] testDataArray) { list.AddRange(testDataArray); @@ -76,10 +101,34 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableList(); // Act - if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) { list.AddRange(new ReadOnlySpan((long[])testData)); } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange(new Span((long[])testData)); + } else if (testData is long[] testDataArray) { list.AddRange(testDataArray); @@ -123,12 +172,43 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableList(); // Act - if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((Array)testData); + list.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IEnumerable)testData); + list.AddRange((IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IReadOnlyList)testData); + list.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) { var readOnlySpan = new ReadOnlySpan((long[])testData); list.AddRange(readOnlySpan); list.AddRange(readOnlySpan); } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + var readOnlySpan = new Span((long[])testData); + list.AddRange(readOnlySpan); + list.AddRange(readOnlySpan); + } else if (testData is long[] testDataArray) { list.AddRange(testDataArray); diff --git a/Recyclable.CollectionsTests/RecyclableLongListTestData.cs b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs index e1da8e3..8a67fbe 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs @@ -1,6 +1,7 @@ using MoreLinq; using Open.Numeric.Primes; using Recyclable.Collections; +using System.Collections; using System.Numerics; namespace Recyclable.CollectionsTests @@ -78,23 +79,21 @@ namespace Recyclable.CollectionsTests { GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - IDisposable disposable; - switch (refType) { case false: long[] testData = CreateTestData(itemsCount).ToArray(); - yield return new object[] { $"int[{itemsCount}]", testData, itemsCount }; - yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testData, itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"List(itemsCount: {itemsCount}", testData.ToList(), itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"RecyclableList(itemsCount: {itemsCount})", disposable = testData.ToRecyclableList(), itemsCount }; - //disposable.Dispose(); - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + yield return new object[] { $"Array[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"ICollection[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"ICollection[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"IEnumerable[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"IEnumerable[{itemsCount}] without non-enumerated count", new EnumerableWithoutCount(testData), itemsCount }; + yield return new object[] { $"IList[{itemsCount}]", new CustomIList(testData), itemsCount }; + yield return new object[] { $"IReadOnlyList[{itemsCount}])", testData, itemsCount }; + yield return new object[] { $"List[{itemsCount}]", testData.ToList(), itemsCount }; + yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"RecyclableList[{itemsCount}]", testData.ToRecyclableList(), itemsCount }; foreach (var sourceBlockSize in BlockSizeVariants) { @@ -105,39 +104,29 @@ namespace Recyclable.CollectionsTests yield return new object[] { - $"RecyclableLongList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", - disposable = testData.ToRecyclableLongList(sourceBlockSize), + $"RecyclableLongList[itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize}]", + testData.ToRecyclableLongList(sourceBlockSize), itemsCount }; - - //disposable.Dispose(); - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); } - yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(testData), itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) with non-enumerated count", testData, itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) without non-enumerated count", new EnumerableWithoutCount(testData), itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - + yield return new object[] { $"Span[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"T[{itemsCount}]", testData, itemsCount }; break; case true: IEnumerable testRefData = CreateRefTestData(itemsCount).ToArray(); - yield return new object[] { $"int[{itemsCount}]", testRefData, itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"List(itemsCount: {itemsCount}", testRefData.ToList(), itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"RecyclableList(itemsCount: {itemsCount})", disposable = testRefData.ToRecyclableList(), itemsCount }; - - //disposable.Dispose(); - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + yield return new object[] { $"Array[{itemsCount}]", testRefData, itemsCount }; + yield return new object[] { $"ICollection(itemsCount: {itemsCount}", testRefData, itemsCount }; + yield return new object[] { $"ICollection(itemsCount: {itemsCount}", testRefData, itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount})", testRefData, itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) without non-enumerated count", new EnumerableWithoutCount(testRefData), itemsCount }; + yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(testRefData), itemsCount }; + yield return new object[] { $"IReadOnlyList(itemsCount: {itemsCount})", testRefData, itemsCount }; + yield return new object[] { $"List(itemsCount: {itemsCount}", testRefData.ToList(), itemsCount }; + yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testRefData, itemsCount }; + yield return new object[] { $"RecyclableList(itemsCount: {itemsCount})", testRefData.ToRecyclableList(), itemsCount }; foreach (var sourceBlockSize in BlockSizeVariants) { @@ -148,24 +137,14 @@ namespace Recyclable.CollectionsTests yield return new object[] { - $"RecyclableLongList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", - disposable = testRefData.ToRecyclableLongList(sourceBlockSize), + $"RecyclableLongList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", + testRefData.ToRecyclableLongList(sourceBlockSize), itemsCount }; - - //disposable.Dispose(); - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); } - yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(testRefData), itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) with non-enumerated count", testRefData, itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - - yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) without non-enumerated count", new EnumerableWithoutCount(testRefData), itemsCount }; - //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - + yield return new object[] { $"Span[{itemsCount}]", testRefData, itemsCount }; + yield return new object[] { $"T[{itemsCount}]", testRefData, itemsCount }; break; } } diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index fd83ed9..cb27286 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Recyclable.Collections; using Recyclable.Collections.Linq; +using System.Collections; using System.Numerics; #pragma warning disable xUnit1026, RCS1163, IDE0060, RCS1235 @@ -41,10 +42,34 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableLongList(minBlockSize: targetBlockSize); // Act - if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) { list.AddRange(new ReadOnlySpan((long[])testData)); } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange(new Span((long[])testData)); + } else if (testData is long[] testDataArray) { list.AddRange(testDataArray); @@ -90,10 +115,34 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableLongList(); // Act - if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) { list.AddRange(new ReadOnlySpan((long[])testData)); } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange(new Span((long[])testData)); + } else if (testData is long[] testDataArray) { list.AddRange(testDataArray); @@ -138,12 +187,43 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableLongList(minBlockSize: targetBlockSize); // Act - if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((Array)testData); + list.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((ICollection)testData); + list.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IEnumerable)testData); + list.AddRange((IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + list.AddRange((IReadOnlyList)testData); + list.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) { var readOnlySpan = new ReadOnlySpan((long[])testData); list.AddRange(readOnlySpan); list.AddRange(readOnlySpan); } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + var readOnlySpan = new Span((long[])testData); + list.AddRange(readOnlySpan); + list.AddRange(readOnlySpan); + } else if (testData is long[] testDataArray) { list.AddRange(testDataArray);