Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Containers.Test/RingBufferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,59 @@ public void Constructor_MinimumLength_IsUsable()
Assert.AreEqual(1, buffer.Count);
Assert.AreEqual(2, buffer.At(0));
}

[TestMethod]
public void Enumerate_PushBackDuringForeach_Throws() =>
AssertModificationDuringForeachThrows(buffer => buffer.PushBack(5));

[TestMethod]
public void Enumerate_ClearDuringForeach_Throws() =>
AssertModificationDuringForeachThrows(buffer => buffer.Clear());

[TestMethod]
public void Enumerate_ResizeDuringForeach_Throws() =>
AssertModificationDuringForeachThrows(buffer => buffer.Resize(8));

[TestMethod]
public void Enumerate_ResampleDuringForeach_Throws() =>
AssertModificationDuringForeachThrows(buffer => buffer.Resample(8));

[TestMethod]
public void Enumerate_ModifiedAfterLastElement_ThrowsOnFinalMoveNext()
{
RingBuffer<int> buffer = new([1, 2], 2);
using IEnumerator<int> enumerator = buffer.GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsTrue(enumerator.MoveNext());

buffer.PushBack(3);

Assert.ThrowsExactly<InvalidOperationException>(() => enumerator.MoveNext());
}

[TestMethod]
public void Enumerate_ModifiedBetweenEnumerations_DoesNotThrow()
{
RingBuffer<int> buffer = new([1, 2, 3, 4], 4);
_ = buffer.ToArray();
buffer.PushBack(5);

Assert.AreSequenceEqual(Enumerable.Range(2, 4), buffer);
}

private static void AssertModificationDuringForeachThrows(Action<RingBuffer<int>> modify)
{
RingBuffer<int> buffer = new([1, 2, 3, 4], 4);

Assert.ThrowsExactly<InvalidOperationException>(() =>
{
foreach (int item in buffer)
{
if (item == 1)
{
modify(buffer);
}
}
});
}
}
16 changes: 15 additions & 1 deletion Containers/RingBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public class RingBuffer<T> : IReadOnlyList<T>
/// </summary>
public int Count { get; private set; } // Track number of valid elements

/// <summary>
/// Incremented by every change, so an enumerator can tell the buffer changed under it.
/// </summary>
private int version;

/// <summary>
/// Gets the element at the specified index in the buffer.
/// </summary>
Expand Down Expand Up @@ -141,6 +146,7 @@ private void AllocateBuffer(int length)
BackIndex = 0;
FrontIndex = 0;
Count = 0;
version++;
}

/// <summary>
Expand Down Expand Up @@ -171,6 +177,7 @@ public void PushBack(T o)

// Advance back index with wraparound using bitwise AND for efficiency
BackIndex = (BackIndex + 1) & (Capacity - 1);
version++;
}

/// <summary>
Expand Down Expand Up @@ -285,12 +292,18 @@ public void Resample(int length)
/// Returns an enumerator that iterates through the buffer.
/// </summary>
/// <returns>An enumerator for the buffer.</returns>
public IEnumerator<T> GetEnumerator()
/// <exception cref="InvalidOperationException">The buffer was modified during enumeration.</exception>
public IEnumerator<T> GetEnumerator() => Enumerate(version);

private IEnumerator<T> Enumerate(int expected)
{
for (int i = 0; i < Count; i++)
{
Enumeration.ThrowIfModified(expected, version);
yield return At(i);
}

Enumeration.ThrowIfModified(expected, version);
}

/// <summary>
Expand Down Expand Up @@ -320,5 +333,6 @@ public void Clear()
BackIndex = 0;
FrontIndex = 0;
Count = 0;
version++;
}
}
Loading