First implementation
This commit is contained in:
1 parent
98c02ef0ed
commit
6a58917120
12 files changed
+662
No files matched your search
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33205.214
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.Collections", "Recyclable.Collections\Recyclable.Collections.csproj", "{7EC0F96D-FB4C-418A-9172-612CC5BCE705}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.CollectionsTests", "Recyclable.CollectionsTests\Recyclable.CollectionsTests.csproj", "{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2C612762-D1AF-4E88-9427-B4750FBECB1A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal class FrequencyTupleComparer<TKey, TValue> : IComparer<(TKey Key, TValue Value)>
|
||||
{
|
||||
private static readonly IComparer<TKey> _baseComparer = Comparer<TKey>.Default;
|
||||
|
||||
public int Compare((TKey Key, TValue Value) x, (TKey Key, TValue Value) y)
|
||||
=> _baseComparer.Compare(x.Key, y.Key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public interface IRandomNumberGenerator
|
||||
{
|
||||
int NextInt32(int startIndex, int endIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
// The source code was taken from https://codereview.stackexchange.com/questions/205407/quicksort-without-recursion.
|
||||
// It was only beautified for .Net 6.0+.
|
||||
public static class ListExtensions
|
||||
{
|
||||
public static void InsertionSort<T>(this IList<T> values, int startIndex, int endIndex, IComparer<T> comparer)
|
||||
{
|
||||
var left = startIndex;
|
||||
while (left < endIndex)
|
||||
{
|
||||
int right = left;
|
||||
T? temp = values[++left];
|
||||
while (right >= startIndex && 0 < comparer.Compare(values[right], temp))
|
||||
{
|
||||
values[right + 1] = values[right--];
|
||||
}
|
||||
|
||||
values[right + 1] = temp;
|
||||
}
|
||||
}
|
||||
public static void InsertionSort<T>(this IList<T> values, IComparer<T> comparer)
|
||||
{
|
||||
values.InsertionSort(0, values.Count - 1, comparer);
|
||||
}
|
||||
public static void InsertionSort<T>(this IList<T> values)
|
||||
{
|
||||
values.InsertionSort(Comparer<T>.Default);
|
||||
}
|
||||
public static void QuickSort<T>(this IList<T> values, int startIndex, int endIndex, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
var range = (startIndex, endIndex);
|
||||
var stack = new Stack<(int, int)>();
|
||||
|
||||
do
|
||||
{
|
||||
startIndex = range.startIndex;
|
||||
endIndex = range.endIndex;
|
||||
|
||||
if (endIndex - startIndex + 1 < 31)
|
||||
{
|
||||
values.InsertionSort(startIndex, endIndex, comparer);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var pivot = values.SampleMedian(startIndex, endIndex, comparer, randomNumberGenerator);
|
||||
var left = startIndex;
|
||||
var right = endIndex;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
while (0 > comparer.Compare(values[left], pivot))
|
||||
{
|
||||
left++;
|
||||
}
|
||||
|
||||
while (0 > comparer.Compare(pivot, values[right]))
|
||||
{
|
||||
right--;
|
||||
}
|
||||
|
||||
if (left <= right)
|
||||
{
|
||||
values.Swap(left++, right--);
|
||||
}
|
||||
}
|
||||
|
||||
if (startIndex < right)
|
||||
{
|
||||
stack.Push((startIndex, right));
|
||||
}
|
||||
|
||||
if (left < endIndex)
|
||||
{
|
||||
stack.Push((left, endIndex));
|
||||
}
|
||||
}
|
||||
while (stack.TryPop(out range));
|
||||
}
|
||||
public static void QuickSort<T>(this IList<T> values, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
values.QuickSort(0, values.Count - 1, comparer, randomNumberGenerator);
|
||||
}
|
||||
public static void QuickSort<T>(this IList<T> values)
|
||||
{
|
||||
values.QuickSort(Comparer<T>.Default, new SystemRandomNumberGenerator());
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T SampleMedian<T>(this IList<T> values, int startIndex, int endIndex, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
var left = randomNumberGenerator.NextInt32(startIndex, endIndex);
|
||||
var middle = randomNumberGenerator.NextInt32(startIndex, endIndex);
|
||||
var right = randomNumberGenerator.NextInt32(startIndex, endIndex);
|
||||
|
||||
if (0 > comparer.Compare(values[right], values[left]))
|
||||
{
|
||||
values.Swap(right, left);
|
||||
}
|
||||
|
||||
if (0 > comparer.Compare(values[middle], values[left]))
|
||||
{
|
||||
values.Swap(middle, left);
|
||||
}
|
||||
|
||||
if (0 > comparer.Compare(values[right], values[middle]))
|
||||
{
|
||||
values.Swap(right, middle);
|
||||
}
|
||||
|
||||
return values[middle];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Swap<T>(this IList<T> values, int xIndex, int yIndex)
|
||||
{
|
||||
(values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageId>Recyclable.Collections</PackageId>
|
||||
<Version>0.0.1</Version>
|
||||
<Authors>Marek Lemańczyk</Authors>
|
||||
<Company>N/A</Company>
|
||||
<Product>Recyclable.Collections</Product>
|
||||
<PackageTags>Recyclable ArrayPool Array Pool List Sortable Sort</PackageTags>
|
||||
<Description>Recyclable.Collections project is an open source framework for operating dynamic lists at performance close to raw arrays. It aims at providing high performance with minimal memory footprint. It implements IList<T>'s interface and is targeted as direct replacements of List<T>, SortableList<T>, PriorityQueue<T> and similar.
|
||||
|
||||
The project & source code is free for educational, open-source & personal use.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="RecyclableSortedList.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public class RecyclableDefaults
|
||||
{
|
||||
public const int BlockSize = 10_240;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public delegate void ItemAdded<T>(long index, T item);
|
||||
|
||||
public class RecyclableList<T> : IDisposable, IList<T>
|
||||
{
|
||||
private static readonly IEqualityComparer<T> _comparer = EqualityComparer<T>.Default;
|
||||
|
||||
private readonly int _blockSize;
|
||||
private readonly List<T[]> _arrays = new();
|
||||
private bool _disposedValue;
|
||||
private long _capacity;
|
||||
|
||||
private ItemAdded<T>? _itemAdded;
|
||||
public event ItemAdded<T> ItemAdded { add => _itemAdded += value; remove => _itemAdded -= value; }
|
||||
|
||||
private static IEnumerable<T> EnumerateElements(List<T[]> arrays, int chunkSize, long totalCount)
|
||||
{
|
||||
long currentCount = 0;
|
||||
for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++)
|
||||
{
|
||||
var array = arrays[arrayIdx];
|
||||
currentCount += chunkSize;
|
||||
switch (currentCount < totalCount)
|
||||
{
|
||||
case true:
|
||||
for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++)
|
||||
{
|
||||
yield return array[valueIdx];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case false:
|
||||
var partialCount = (int)(totalCount % chunkSize);
|
||||
int maxCount = partialCount > 0 ? partialCount : chunkSize;
|
||||
for (var valueIdx = 0; valueIdx < maxCount; valueIdx++)
|
||||
{
|
||||
yield return array[valueIdx];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static T[] RentArray(int minSize) => ArrayPool<T>.Shared.Rent(minSize);
|
||||
private static void ReturnArray(in T[] array) => ArrayPool<T>.Shared.Return(array);
|
||||
|
||||
public RecyclableList(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
}
|
||||
|
||||
public RecyclableList(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
foreach (var item in source)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public T this[long index]
|
||||
{
|
||||
get => _arrays[(int)(index / _blockSize)][index % _blockSize];
|
||||
|
||||
set
|
||||
{
|
||||
_arrays[(int)(index / _blockSize)][index % _blockSize] = value;
|
||||
_itemAdded?.Invoke(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => this[(long)index];
|
||||
set => this[(long)index] = value;
|
||||
}
|
||||
|
||||
public int Count => LongCount <= int.MaxValue ? (int)LongCount : int.MaxValue;
|
||||
public long LongCount { get; protected set; }
|
||||
public bool IsReadOnly { get; } = false;
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
if (LongCount == _capacity)
|
||||
{
|
||||
var newArray = RentArray(_blockSize);
|
||||
_arrays.Add(newArray);
|
||||
_capacity += _blockSize;
|
||||
}
|
||||
|
||||
var newIndex = LongCount;
|
||||
this[newIndex] = item;
|
||||
// Don't merge these 2 lines, otherwise we'll have increased count
|
||||
// before the item is actually added.
|
||||
LongCount++;
|
||||
_itemAdded?.Invoke(newIndex, item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReturnArray(_arrays[arrayIdx]);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We want to try returning as many arrays, as possible, before
|
||||
// the list is cleared.
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure the list is cleared so that we no longer use any of the arrays
|
||||
LongCount = 0;
|
||||
_arrays.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) => _arrays.Any(x => x.Contains(item));
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
Span<T> arrayMemory = array.AsSpan()[arrayIndex..];
|
||||
for (var arrayIdx = 0; arrayIdx < _arrays.Count - 1; arrayIdx++)
|
||||
{
|
||||
T[] partialArray = _arrays[arrayIdx];
|
||||
ReadOnlySpan<T> sourceMemory = partialArray.AsSpan();
|
||||
sourceMemory = sourceMemory[.._blockSize];
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
arrayMemory = arrayMemory[_blockSize..];
|
||||
}
|
||||
|
||||
if (_arrays.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<T> sourceMemory = _arrays[^1].AsSpan();
|
||||
var maxCount = Count % _blockSize;
|
||||
sourceMemory = maxCount > 0
|
||||
? sourceMemory[..maxCount]
|
||||
: sourceMemory[.._blockSize];
|
||||
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => EnumerateElements(_arrays, _blockSize, LongCount).GetEnumerator();
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
|
||||
{
|
||||
var array = _arrays[arrayIdx];
|
||||
Span<T> arrayMemory = ((T[]?)array).AsSpan();
|
||||
for (var memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++)
|
||||
{
|
||||
if (_comparer.Equals(arrayMemory[memoryIdx], item))
|
||||
{
|
||||
return (arrayIdx * _blockSize) + memoryIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~RecyclableList()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public static class RecyclableListExtensions
|
||||
{
|
||||
public static RecyclableList<T> ToRecyclableList<T>(this IList<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
|
||||
public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public class SystemRandomNumberGenerator : IRandomNumberGenerator
|
||||
{
|
||||
private static readonly Random _random = new();
|
||||
|
||||
public int NextInt32(int startIndex, int endIndex) => _random.Next(startIndex, endIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="RecyclableSortedListTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Recyclable.Collections\Recyclable.Collections.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,193 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
[TestClass()]
|
||||
public class RecyclableListTests
|
||||
{
|
||||
private const int _totalObjectCount = 5000;
|
||||
private static RecyclableList<int> NewRecyclableList => CreateReversedRecyclableList(_testData);
|
||||
private static readonly IEnumerable<int> _testData = Enumerable.Range(1, _totalObjectCount);
|
||||
private static RecyclableList<int> CreateReversedRecyclableList(IEnumerable<int> source) => new(source);
|
||||
|
||||
[TestMethod]
|
||||
public void AddShouldAddItems()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = new RecyclableList<int>();
|
||||
|
||||
// Act
|
||||
const int itemsCount = 5;
|
||||
foreach (var index in Enumerable.Range(1, itemsCount))
|
||||
{
|
||||
sut.Add(index);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = sut.Count.Should().Be(itemsCount, "we added so many items");
|
||||
for (var index = 0; index < itemsCount; index++)
|
||||
{
|
||||
var actual = sut[index];
|
||||
_ = actual.Should().Be(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddShouldAcceptDuplicates()
|
||||
{
|
||||
// Prepare
|
||||
int[] testNumbers = new[] { 1, 2, 2, 3 };
|
||||
|
||||
// Act
|
||||
using var sut = new RecyclableList<int>();
|
||||
foreach (var index in testNumbers)
|
||||
{
|
||||
sut.Add(index);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = sut.LongCount.Should().Be(testNumbers.Length);
|
||||
_ = sut.Should().BeEquivalentTo(testNumbers);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConstructorShouldAcceptDuplicates()
|
||||
{
|
||||
// Prepare
|
||||
int[] testNumbers = new[] { 1, 2, 2, 3 };
|
||||
|
||||
// Act
|
||||
using var sut = new RecyclableList<int>(testNumbers);
|
||||
|
||||
// Validate
|
||||
_ = sut.LongCount.Should().Be(testNumbers.Length);
|
||||
_ = sut.Should().BeEquivalentTo(testNumbers);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InitializeSortClearShouldSucceed()
|
||||
{
|
||||
var expectedList = _testData;
|
||||
|
||||
// Act
|
||||
using RecyclableList<int> actualList = NewRecyclableList;
|
||||
actualList.QuickSort();
|
||||
|
||||
// Validate
|
||||
_ = actualList.LongCount.Should().Be(_totalObjectCount);
|
||||
_ = actualList.Count.Should().Be(_totalObjectCount);
|
||||
_ = actualList.Should().Equal(expectedList);
|
||||
_ = expectedList.Should().AllSatisfy(expected => actualList.Contains(expected));
|
||||
|
||||
// Act
|
||||
actualList.Clear();
|
||||
|
||||
// Validate
|
||||
_ = actualList.LongCount.Should().Be(0);
|
||||
_ = actualList.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyToShouldCopyAllItems()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
var actual = new int[sut.Count];
|
||||
sut.CopyTo(actual, 0);
|
||||
_ = actual.Should().BeEquivalentTo(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetEnumeratorShouldYieldAllItems()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
var actual = new List<int>();
|
||||
var enumerator = sut.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = actual.Should().BeEquivalentTo(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IndexOfShouldFindTheIndexes()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act & Validate
|
||||
foreach(var index in _testData)
|
||||
{
|
||||
var actual = sut.IndexOf(index);
|
||||
_ = actual.Should().Be(index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void InsertShouldRaiseNotSupportedException()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
sut.Insert(0, -1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void RemoveShouldRaiseNotSupportedException()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
bool removed = sut.Remove(_testData.First());
|
||||
_ = removed.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void RemoveAtShouldRaiseNotSupportedException()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
sut.RemoveAt(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldSucceed()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
sut.Dispose();
|
||||
|
||||
// Validate
|
||||
_ = sut.LongCount.Should().Be(0L);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConsecutiveDisposeShouldSucceed()
|
||||
{
|
||||
// Prepare
|
||||
using var sut = NewRecyclableList;
|
||||
sut.Dispose();
|
||||
|
||||
// Act
|
||||
sut.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
Reference in new issue
Block a user