diff --git a/Recyclable.Collections/RecyclableQueue.AddRange.cs b/Recyclable.Collections/RecyclableQueue.AddRange.cs new file mode 100644 index 0000000..de16f1b --- /dev/null +++ b/Recyclable.Collections/RecyclableQueue.AddRange.cs @@ -0,0 +1,928 @@ +using System.Collections; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Numerics; +using Recyclable.Collections.Pools; + +namespace Recyclable.Collections +{ + internal static class zRecyclableQueueAddRange + { + private static class AddRangeHelper + { + private static readonly Action, object>? _dictionaryAdder; + private static readonly Action, object>? _sortedListAdder; + private static readonly Type? _dictionaryType; + private static readonly Type? _sortedListType; + + static AddRangeHelper() + { + Type elementType = typeof(T); + if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + Type[] args = elementType.GetGenericArguments(); + _dictionaryType = typeof(RecyclableDictionary<,>).MakeGenericType(args); + MethodInfo? method = typeof(zRecyclableQueueAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableQueue<>).MakeGenericType(elementType), _dictionaryType }, + modifiers: null); + if (method != null) + { + var queueParam = Expression.Parameter(typeof(RecyclableQueue)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _dictionaryType)); + _dictionaryAdder = Expression.Lambda, object>>(call, queueParam, objParam).Compile(); + } + } + else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>)) + { + Type[] args = elementType.GetGenericArguments(); + _sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args); + MethodInfo? method = typeof(zRecyclableQueueAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableQueue<>).MakeGenericType(elementType), _sortedListType }, + modifiers: null); + if (method != null) + { + var queueParam = Expression.Parameter(typeof(RecyclableQueue)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _sortedListType)); + _sortedListAdder = Expression.Lambda, object>>(call, queueParam, objParam).Compile(); + } + } + } + + internal static bool TryAddRange(RecyclableQueue queue, IEnumerable items) + { + if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items)) + { + _dictionaryAdder(queue, items); + return true; + } + + if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items)) + { + _sortedListAdder(queue, items); + return true; + } + + return false; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, ReadOnlySpan items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + EnsureCapacity(queue, queue._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + items.Slice(copied, toCopy).CopyTo(new Span(chunk.Value, index, toCopy)); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, Span items) => AddRange(queue, (ReadOnlySpan)items); + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, T[] items) => AddRange(queue, (ReadOnlySpan)items); + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, in Array items) + { + int count = items.Length; + if (count == 0) + { + return; + } + + EnsureCapacity(queue, queue._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + int lower = items.GetLowerBound(0); + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(items, lower + copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, List items) => AddRange(queue, CollectionsMarshal.AsSpan(items)); + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, ICollection items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + T[] buffer = count >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)count))) + : new T[count]; + + items.CopyTo(buffer, 0); + + EnsureCapacity(queue, queue._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(buffer, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + + if (count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(buffer, RecyclableQueue._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, ICollection items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + T[] buffer = count >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)count))) + : new T[count]; + + items.CopyTo(buffer, 0); + + EnsureCapacity(queue, queue._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(buffer, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + + if (count >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(buffer, RecyclableQueue._needsClearing); + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableList items) + { + int count = items._count; + if (count == 0) + { + return; + } + + EnsureCapacity(queue, queue._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + T[] source = items._memoryBlock; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Grow(RecyclableQueue queue) + { + int newSize; + if (queue._capacity >= RecyclableDefaults.MaxPooledBlockSize) + { + newSize = RecyclableDefaults.MaxPooledBlockSize; + } + else + { + long doubled = queue._capacity << 1; + newSize = (int)Math.Min(doubled - queue._capacity, RecyclableDefaults.MaxPooledBlockSize); + } + + var newChunk = BiDirectionalRecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < newSize) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableQueue._needsClearing); + } + + newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + } + + newChunk.Top = 0; + newChunk.Bottom = 0; + newChunk.Previous = queue._tail; + newChunk.Next = null; + queue._tail.Next = newChunk; + queue._tail = newChunk; + queue._capacity += newSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Grow(RecyclableQueue queue, long additionalCapacity) + { + int newSize = additionalCapacity >= RecyclableDefaults.MaxPooledBlockSize + ? RecyclableDefaults.MaxPooledBlockSize + : (int)Math.Min(BitOperations.RoundUpToPowerOf2((uint)additionalCapacity), RecyclableDefaults.MaxPooledBlockSize); + + var newChunk = BiDirectionalRecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < newSize) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableQueue._needsClearing); + } + + newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + } + + newChunk.Top = 0; + newChunk.Bottom = 0; + newChunk.Previous = queue._tail; + newChunk.Next = null; + queue._tail.Next = newChunk; + queue._tail = newChunk; + queue._capacity += newSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(RecyclableQueue queue, long requiredCapacity) + { + while (queue._capacity < requiredCapacity) + { + Grow(queue, requiredCapacity - queue._capacity); + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableLongList items) + { + long longCount = items._longCount; + if (longCount == 0) + { + return; + } + + int blockSize = items._blockSize; + int fullBlocks = (int)(longCount >> items._blockSizePow2BitShift); + int lastBlockLength = (int)(longCount & items._blockSizeMinus1); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + for (int blockIndex = 0; blockIndex < fullBlocks; blockIndex++) + { + T[] block = items._memoryBlocks[blockIndex]; + int sourceIndex = 0; + while (sourceIndex < blockSize) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, blockSize - sourceIndex); + Array.Copy(block, sourceIndex, chunk.Value, index, toCopy); + sourceIndex += toCopy; + index += toCopy; + } + } + + if (lastBlockLength > 0) + { + T[] block = items._memoryBlocks[fullBlocks]; + int sourceIndex = 0; + while (sourceIndex < lastBlockLength) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, lastBlockLength - sourceIndex); + Array.Copy(block, sourceIndex, chunk.Value, index, toCopy); + sourceIndex += toCopy; + index += toCopy; + } + } + + chunk.Top = index; + queue._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableHashSet items) + { + int count = items._count; + if (count == 0) + { + return; + } + + var entries = items._entries; + int shift = items._blockShift, + mask = items._blockSizeMinus1; + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + var destination = chunk.Value; + for (int i = 0; i < toCopy; i++) + { + destination[index + i] = entries[(copied + i) >> shift][(copied + i) & mask].Value; + } + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableStack items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk destChunk = queue._tail; + int destIndex = destChunk.Top; + int destLength = destChunk.Value.Length; + + RecyclableArrayPoolChunk sourceChunk = items._current; + while (sourceChunk.Previous != null) + { + sourceChunk = sourceChunk.Previous; + } + + while (sourceChunk != null) + { + T[] source = sourceChunk.Value; + int sourceCount = sourceChunk.Index; + int sourceIndex = 0; + + while (sourceIndex < sourceCount) + { + if (destIndex == destLength) + { + destChunk.Top = destIndex; + Grow(queue); + destChunk = queue._tail; + destIndex = destChunk.Top; + destLength = destChunk.Value.Length; + } + + int toCopy = Math.Min(destLength - destIndex, sourceCount - sourceIndex); + Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy); + sourceIndex += toCopy; + destIndex += toCopy; + } + + sourceChunk = sourceChunk.Next!; + } + + destChunk.Top = destIndex; + queue._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableSortedSet items) + { + int count = items._count; + if (count == 0) + { + return; + } + + T[] source = items._items; + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableLinkedList items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk destChunk = queue._tail; + int destIndex = destChunk.Top; + int destLength = destChunk.Value.Length; + + var sourceChunk = items._head; + while (sourceChunk != null) + { + int sourceIndex = sourceChunk.Bottom; + int sourceTop = sourceChunk.Top; + T[] source = sourceChunk.Value; + + while (sourceIndex < sourceTop) + { + if (destIndex == destLength) + { + destChunk.Top = destIndex; + Grow(queue); + destChunk = queue._tail; + destIndex = destChunk.Top; + destLength = destChunk.Value.Length; + } + + int toCopy = Math.Min(destLength - destIndex, sourceTop - sourceIndex); + Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy); + sourceIndex += toCopy; + destIndex += toCopy; + } + + sourceChunk = sourceChunk.Next; + } + + destChunk.Top = destIndex; + queue._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclablePriorityQueue items) + { + int count = items._size; + if (count == 0) + { + return; + } + + T[] source = items._heap; + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, RecyclableQueue items) + { + long longCount = items._count; + if (longCount == 0) + { + return; + } + + BiDirectionalRecyclableArrayPoolChunk destChunk = queue._tail; + int destIndex = destChunk.Top; + int destLength = destChunk.Value.Length; + + EnsureCapacity(queue, queue._count + longCount); + + var sourceChunk = items._head; + while (sourceChunk != null) + { + int sourceIndex = sourceChunk.Bottom; + int sourceTop = sourceChunk.Top; + T[] source = sourceChunk.Value; + + while (sourceIndex < sourceTop) + { + if (destIndex == destLength) + { + destChunk.Top = destIndex; + destChunk = destChunk.Next!; + destIndex = destChunk.Top; + destLength = destChunk.Value.Length; + } + + int toCopy = Math.Min(destLength - destIndex, sourceTop - sourceIndex); + Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy); + sourceIndex += toCopy; + destIndex += toCopy; + } + + sourceChunk = sourceChunk.Next; + } + + destChunk.Top = destIndex; + queue._count += longCount; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue> queue, RecyclableDictionary items) + where TKey : notnull + { + int count = items._count; + if (count == 0) + { + return; + } + + EnsureCapacity(queue, queue._count + count); + + var entries = items._entries; + int shift = items._blockShift, + mask = items._blockSizeMinus1; + + BiDirectionalRecyclableArrayPoolChunk> chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + var destination = chunk.Value; + int local = copied; + for (int i = 0; i < toCopy; i++) + { + ref var entry = ref entries[(local + i) >> shift][(local + i) & mask]; + destination[index + i] = new KeyValuePair(entry.Key, entry.Value); + } + + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue<(TKey Key, TValue Value)> queue, RecyclableSortedList items) + where TKey : notnull + { + int count = items._count; + if (count == 0) + { + return; + } + + EnsureCapacity(queue, queue._count + count); + + TKey[] keys = items._keys; + TValue[] values = items._values; + + BiDirectionalRecyclableArrayPoolChunk<(TKey Key, TValue Value)> chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + int copied = 0; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + var destination = chunk.Value; + for (int i = 0; i < toCopy; i++) + { + destination[index + i] = (keys[copied + i], values[copied + i]); + } + + copied += toCopy; + index += toCopy; + } + + chunk.Top = index; + queue._count += count; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, IReadOnlyList items) + { + int count = items.Count; + if (count == 0) + { + return; + } + + EnsureCapacity(queue, queue._count + count); + + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + for (int i = 0; i < count; i++) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + chunk.Value[index++] = items[i]; + } + + chunk.Top = index; + queue._count += count; + } + + internal static IEnumerator AddRange(this RecyclableQueue queue, IEnumerable source) + { + IEnumerator enumerator = source.GetEnumerator(); + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + while (enumerator.MoveNext()) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + chunk.Value[index++] = (T)enumerator.Current!; + queue._count++; + } + + chunk.Top = index; + return enumerator; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + internal static void AddRange(this RecyclableQueue queue, IEnumerable items) + { + if (items is RecyclableList recyclableList) + { + AddRange(queue, recyclableList); + } + else if (items is RecyclableLongList recyclableLongList) + { + AddRange(queue, recyclableLongList); + } + else if (items is T[] array) + { + AddRange(queue, array); + } + else if (items is List list) + { + AddRange(queue, list); + } + else if (items is ICollection iCollection) + { + AddRange(queue, iCollection); + } + else if (items is ICollection collection) + { + AddRange(queue, collection); + } + else if (items is RecyclableHashSet hashSet) + { + AddRange(queue, hashSet); + } + else if (items is RecyclableStack stack) + { + AddRange(queue, stack); + } + else if (items is RecyclableSortedSet sortedSet) + { + AddRange(queue, sortedSet); + } + else if (items is RecyclableLinkedList linkedList) + { + AddRange(queue, linkedList); + } + else if (items is RecyclablePriorityQueue priorityQueue) + { + AddRange(queue, priorityQueue); + } + else if (items is RecyclableQueue queueSource) + { + AddRange(queue, queueSource); + } + else if (AddRangeHelper.TryAddRange(queue, items)) + { + } + else if (items is IReadOnlyList readOnlyList) + { + AddRange(queue, readOnlyList); + } + else + { + using IEnumerator enumerator = items.GetEnumerator(); + BiDirectionalRecyclableArrayPoolChunk chunk = queue._tail; + int index = chunk.Top; + int chunkLength = chunk.Value.Length; + + while (enumerator.MoveNext()) + { + if (index == chunkLength) + { + chunk.Top = index; + Grow(queue); + chunk = queue._tail; + index = chunk.Top; + chunkLength = chunk.Value.Length; + } + + chunk.Value[index++] = enumerator.Current; + queue._count++; + } + + chunk.Top = index; + } + } + } +} diff --git a/Recyclable.Collections/RecyclableStack.AddRange.cs b/Recyclable.Collections/RecyclableStack.AddRange.cs index f343eaf..c6afdab 100644 --- a/Recyclable.Collections/RecyclableStack.AddRange.cs +++ b/Recyclable.Collections/RecyclableStack.AddRange.cs @@ -158,7 +158,118 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] internal static void AddRange(this RecyclableStack stack, RecyclableList items) { - AddRange(stack, new ReadOnlySpan(items._memoryBlock, 0, items._count)); + int count = items._count; + if (count == 0) + { + return; + } + + EnsureCapacity(stack, stack._count + count); + + RecyclableArrayPoolChunk chunk = stack._current; + int index = chunk.Index; + int chunkLength = chunk.Value.Length; + int copied = 0; + + T[] source = items._memoryBlock; + + while (copied < count) + { + if (index == chunkLength) + { + chunk.Index = index; + Grow(stack); + chunk = stack._current; + index = chunk.Index; + chunkLength = chunk.Value.Length; + } + + int toCopy = Math.Min(chunkLength - index, count - copied); + Array.Copy(source, copied, chunk.Value, index, toCopy); + copied += toCopy; + index += toCopy; + } + + chunk.Index = index; + stack._count += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Grow(RecyclableStack stack) + { + int newSize; + if (stack._capacity >= RecyclableDefaults.MaxPooledBlockSize) + { + newSize = RecyclableDefaults.MaxPooledBlockSize; + } + else + { + long doubled = stack._capacity << 1; + newSize = (int)Math.Min(doubled - stack._capacity, RecyclableDefaults.MaxPooledBlockSize); + } + + var newChunk = RecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < newSize) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableStack._needsClearing); + } + + newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + } + + newChunk.Index = 0; + newChunk.Previous = stack._current; + newChunk.Next = null; + stack._current.Next = newChunk; + stack._current = newChunk; + stack._capacity += newSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Grow(RecyclableStack stack, long additionalCapacity) + { + int newSize = additionalCapacity >= RecyclableDefaults.MaxPooledBlockSize + ? RecyclableDefaults.MaxPooledBlockSize + : (int)Math.Min(BitOperations.RoundUpToPowerOf2((uint)additionalCapacity), RecyclableDefaults.MaxPooledBlockSize); + + var newChunk = RecyclableArrayPoolChunkPool.Rent(); + if (newChunk.Value.Length < newSize) + { + if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength) + { + RecyclableArrayPool.ReturnShared(newChunk.Value, RecyclableStack._needsClearing); + } + + newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) + : new T[newSize]; + } + + newChunk.Index = 0; + newChunk.Previous = stack._current; + newChunk.Next = null; + stack._current.Next = newChunk; + stack._current = newChunk; + stack._capacity += newSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(RecyclableStack stack, long requiredCapacity) + { + if (stack._capacity >= requiredCapacity) + { + return; + } + + long newCapacity = (long)Math.Min( + BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity), + (ulong)RecyclableDefaults.MaxPooledBlockSize); + + Grow(stack, newCapacity - stack._capacity); } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index e5bd8a2..f05f57f 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -199,23 +199,27 @@ namespace Recyclable.Collections public bool MoveNext() { - if (_chunk == null) + while (true) { - return false; - } - - if (_index < 0) - { - _chunk = _chunk.Previous; if (_chunk == null) { return false; } - _index = _chunk.Index - 1; - } - _current = _chunk.Value[_index--]; - return true; + if (_index < 0) + { + _chunk = _chunk.Previous; + if (_chunk == null) + { + return false; + } + _index = _chunk.Index - 1; + continue; + } + + _current = _chunk.Value[_index--]; + return true; + } } public void Reset() diff --git a/Recyclable.CollectionsTests/RecyclableQueueTests.cs b/Recyclable.CollectionsTests/RecyclableQueueTests.cs index e0e19df..00eb715 100644 --- a/Recyclable.CollectionsTests/RecyclableQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclableQueueTests.cs @@ -116,10 +116,10 @@ namespace Recyclable.CollectionsTests _ = list.LongCount.Should().Be(0); } - [Theory] - [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] - public void GetEnumeratorShouldYieldAllItemsInTheSameOrder(int itemsCount) - { + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void GetEnumeratorShouldYieldAllItemsInTheSameOrder(int itemsCount) + { // Prepare var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray(); using var list = new RecyclableQueue(data); @@ -133,8 +133,530 @@ namespace Recyclable.CollectionsTests } // Validate - _ = actual.Should().ContainInConsecutiveOrder(data) - .And.BeEquivalentTo(data); - } - } + _ = actual.Should().ContainInConsecutiveOrder(data) + .And.BeEquivalentTo(data); + } + + [Fact] + public void AddRangeShouldAcceptNulls() + { + using var queue = new RecyclableQueue(); + queue.AddRange(new long?[] { null, default }); + _ = queue.Should().HaveCount(2).And.AllSatisfy(x => x.Should().BeNull()); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, int itemsCount) + { + using var queue = new RecyclableQueue(); + + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((System.Collections.ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + _ = queue.AddRange((System.Collections.IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange(new ReadOnlySpan((long[])testData)); + } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange(new Span((long[])testData)); + } + else if (testData is long[] array) + { + queue.AddRange(array); + } + else if (testData is List list) + { + queue.AddRange(list); + } + else if (testData is RecyclableList rList) + { + queue.AddRange(rList); + } + else if (testData is RecyclableLongList rLongList) + { + queue.AddRange(rLongList); + } + else if (testData is IList iList) + { + queue.AddRange((ICollection)iList); + } + else if (testData is IEnumerable enumerable) + { + queue.AddRange(enumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + List expected = new(itemsCount); + expected.AddRange(testData); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData, int itemsCount) + { + using var queue = new RecyclableQueue(); + + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((System.Collections.ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + _ = queue.AddRange((System.Collections.IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange(new ReadOnlySpan((long[])testData)); + } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange(new Span((long[])testData)); + } + else if (testData is long[] array) + { + queue.AddRange(array); + } + else if (testData is List list) + { + queue.AddRange(list); + } + else if (testData is RecyclableList rList) + { + queue.AddRange(rList); + } + else if (testData is RecyclableLongList rLongList) + { + queue.AddRange(rLongList); + } + else if (testData is IList iList) + { + queue.AddRange((ICollection)iList); + } + else if (testData is IEnumerable enumerable) + { + queue.AddRange(enumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + List expected = new(itemsCount); + expected.AddRange(testData); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, int itemsCount) + { + using var queue = new RecyclableQueue(); + itemsCount = checked(itemsCount << 1); + + if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((Array)testData); + queue.AddRange((Array)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((System.Collections.ICollection)testData); + queue.AddRange((System.Collections.ICollection)testData); + } + else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((ICollection)testData); + queue.AddRange((ICollection)testData); + } + else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase)) + { + _ = queue.AddRange((System.Collections.IEnumerable)testData); + _ = queue.AddRange((System.Collections.IEnumerable)testData); + } + else if (testCase.Contains("IReadOnlyList[", StringComparison.OrdinalIgnoreCase)) + { + queue.AddRange((IReadOnlyList)testData); + queue.AddRange((IReadOnlyList)testData); + } + else if (testCase.Contains("ReadOnlySpan[", StringComparison.OrdinalIgnoreCase)) + { + var span = new ReadOnlySpan((long[])testData); + queue.AddRange(span); + queue.AddRange(span); + } + else if (testCase.Contains("Span[", StringComparison.OrdinalIgnoreCase)) + { + var span = new Span((long[])testData); + queue.AddRange(span); + queue.AddRange(span); + } + else if (testData is long[] array) + { + queue.AddRange(array); + queue.AddRange(array); + } + else if (testData is List list) + { + queue.AddRange(list); + queue.AddRange(list); + } + else if (testData is RecyclableList rList) + { + queue.AddRange(rList); + queue.AddRange(rList); + } + else if (testData is RecyclableLongList rLongList) + { + queue.AddRange(rLongList); + queue.AddRange(rLongList); + } + else if (testData is IList iList) + { + queue.AddRange((ICollection)iList); + queue.AddRange((ICollection)iList); + } + else if (testData is IEnumerable enumerable) + { + queue.AddRange(enumerable); + queue.AddRange(enumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + List expected = new(itemsCount); + expected.AddRange(testData); + expected.AddRange(testData); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddStackItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableStack(data); + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromStack(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableStack(data); + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddSortedSetItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedSet(); + foreach (long item in data) + { + source.Add(item); + } + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromSortedSet(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedSet(); + foreach (long item in data) + { + source.Add(item); + } + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddLinkedListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableLinkedList(data); + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromLinkedList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableLinkedList(data); + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddPriorityQueueItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclablePriorityQueue(); + foreach (long item in data) + { + source.Enqueue(item); + } + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromPriorityQueue(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclablePriorityQueue(); + foreach (long item in data) + { + source.Enqueue(item); + } + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddQueueItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableQueue(data); + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(data); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromQueue(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableQueue(data); + using var queue = new RecyclableQueue(); + + queue.AddRange(source); + queue.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddDictionaryItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableDictionary(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var queue = new RecyclableQueue>(); + + queue.AddRange(source); + + List> expected = new(itemsCount); + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromDictionary(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableDictionary(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var queue = new RecyclableQueue>(); + + queue.AddRange(source); + queue.AddRange(source); + + List> expected = new(itemsCount * 2); + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + for (int i = 0; i < data.Length; i++) + { + expected.Add(new KeyValuePair(i, data[i])); + } + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedList(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var queue = new RecyclableQueue<(int Key, long Value)>(); + + queue.AddRange(source); + + List<(int Key, long Value)> expected = new(itemsCount); + for (int i = 0; i < data.Length; i++) + { + expected.Add((i, data[i])); + } + + _ = queue.Count.Should().Be(itemsCount); + _ = queue.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromSortedList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableSortedList(); + for (int i = 0; i < data.Length; i++) + { + source.Add(i, data[i]); + } + using var queue = new RecyclableQueue<(int Key, long Value)>(); + + queue.AddRange(source); + queue.AddRange(source); + + List<(int Key, long Value)> expected = new(itemsCount * 2); + for (int i = 0; i < data.Length; i++) + { + expected.Add((i, data[i])); + } + for (int i = 0; i < data.Length; i++) + { + expected.Add((i, data[i])); + } + + _ = queue.Count.Should().Be(itemsCount * 2); + _ = queue.Should().Equal(expected); + } + } } diff --git a/Recyclable.CollectionsTests/RecyclableStackTests.cs b/Recyclable.CollectionsTests/RecyclableStackTests.cs index d215e63..4d91bfc 100644 --- a/Recyclable.CollectionsTests/RecyclableStackTests.cs +++ b/Recyclable.CollectionsTests/RecyclableStackTests.cs @@ -366,5 +366,43 @@ namespace Recyclable.CollectionsTests _ = stack.Should().Equal(expected); } - } + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddListItemsInCorrectOrder(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableList(data); + using var stack = new RecyclableStack(); + + stack.AddRange(source); + + List expected = new(itemsCount); + expected.AddRange(data); + expected.Reverse(); + + _ = stack.Count.Should().Be(itemsCount); + _ = stack.Should().Equal(expected); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItemsFromList(int itemsCount) + { + long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray(); + using var source = new RecyclableList(data); + using var stack = new RecyclableStack(); + + stack.AddRange(source); + stack.AddRange(source); + + List expected = new(itemsCount * 2); + expected.AddRange(data); + expected.AddRange(data); + expected.Reverse(); + + _ = stack.Count.Should().Be(itemsCount * 2); + _ = stack.Should().Equal(expected); + } + + } }