csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
699 lines
24 KiB
699 lines
24 KiB
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
/*=============================================================================
|
|
**
|
|
**
|
|
** Purpose: An array implementation of a generic stack.
|
|
**
|
|
**
|
|
=============================================================================*/
|
|
|
|
using System;
|
|
using System.Buffers;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.Serialization;
|
|
using System.Threading;
|
|
|
|
namespace Avalonia.Collections.Pooled
|
|
{
|
|
/// <summary>
|
|
/// A simple stack of objects. Internally it is implemented as an array,
|
|
/// so Push can be O(n). Pop is O(1).
|
|
/// </summary>
|
|
[DebuggerTypeProxy(typeof(StackDebugView<>))]
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
[Serializable]
|
|
public class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback
|
|
{
|
|
[NonSerialized]
|
|
private ArrayPool<T> _pool;
|
|
[NonSerialized]
|
|
private object? _syncRoot;
|
|
|
|
private T[] _array; // Storage for stack elements. Do not rename (binary serialization)
|
|
private int _size; // Number of items in the stack. Do not rename (binary serialization)
|
|
private int _version; // Used to keep enumerator in sync w/ collection. Do not rename (binary serialization)
|
|
private readonly bool _clearOnFree;
|
|
|
|
private const int DefaultCapacity = 4;
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Create a stack with the default initial capacity.
|
|
/// </summary>
|
|
public PooledStack() : this(ClearMode.Auto, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Create a stack with the default initial capacity.
|
|
/// </summary>
|
|
public PooledStack(ClearMode clearMode) : this(clearMode, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Create a stack with the default initial capacity.
|
|
/// </summary>
|
|
public PooledStack(ArrayPool<T> customPool) : this(ClearMode.Auto, customPool) { }
|
|
|
|
/// <summary>
|
|
/// Create a stack with the default initial capacity and a custom ArrayPool.
|
|
/// </summary>
|
|
public PooledStack(ClearMode clearMode, ArrayPool<T> customPool)
|
|
{
|
|
_pool = customPool ?? ArrayPool<T>.Shared;
|
|
_array = Array.Empty<T>();
|
|
_clearOnFree = ShouldClear(clearMode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a stack with a specific initial capacity. The initial capacity
|
|
/// must be a non-negative number.
|
|
/// </summary>
|
|
public PooledStack(int capacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Create a stack with a specific initial capacity. The initial capacity
|
|
/// must be a non-negative number.
|
|
/// </summary>
|
|
public PooledStack(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Create a stack with a specific initial capacity. The initial capacity
|
|
/// must be a non-negative number.
|
|
/// </summary>
|
|
public PooledStack(int capacity, ArrayPool<T> customPool) : this(capacity, ClearMode.Auto, customPool) { }
|
|
|
|
/// <summary>
|
|
/// Create a stack with a specific initial capacity. The initial capacity
|
|
/// must be a non-negative number.
|
|
/// </summary>
|
|
public PooledStack(int capacity, ClearMode clearMode, ArrayPool<T> customPool)
|
|
{
|
|
if (capacity < 0)
|
|
{
|
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity,
|
|
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
|
|
}
|
|
_pool = customPool ?? ArrayPool<T>.Shared;
|
|
_array = _pool.Rent(capacity);
|
|
_clearOnFree = ShouldClear(clearMode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(IEnumerable<T> enumerable) : this(enumerable, ClearMode.Auto, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode) : this(enumerable, clearMode, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(IEnumerable<T> enumerable, ArrayPool<T> customPool) : this(enumerable, ClearMode.Auto, customPool) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode, ArrayPool<T> customPool)
|
|
{
|
|
_pool = customPool ?? ArrayPool<T>.Shared;
|
|
_clearOnFree = ShouldClear(clearMode);
|
|
|
|
switch (enumerable)
|
|
{
|
|
case null:
|
|
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable);
|
|
break;
|
|
|
|
case ICollection<T> collection:
|
|
if (collection.Count == 0)
|
|
{
|
|
_array = Array.Empty<T>();
|
|
}
|
|
else
|
|
{
|
|
_array = _pool.Rent(collection.Count);
|
|
collection.CopyTo(_array, 0);
|
|
_size = collection.Count;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
using (var list = new PooledList<T>(enumerable))
|
|
{
|
|
_array = _pool.Rent(list.Count);
|
|
list.Span.CopyTo(_array);
|
|
_size = list.Count;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(T[] array, ArrayPool<T> customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(T[] array, ClearMode clearMode, ArrayPool<T> customPool) : this(array.AsSpan(), clearMode, customPool) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(ReadOnlySpan<T> span) : this(span, ClearMode.Auto, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode) : this(span, clearMode, ArrayPool<T>.Shared) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(ReadOnlySpan<T> span, ArrayPool<T> customPool) : this(span, ClearMode.Auto, customPool) { }
|
|
|
|
/// <summary>
|
|
/// Fills a Stack with the contents of a particular collection. The items are
|
|
/// pushed onto the stack in the same order they are read by the enumerator.
|
|
/// </summary>
|
|
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode, ArrayPool<T> customPool)
|
|
{
|
|
_pool = customPool ?? ArrayPool<T>.Shared;
|
|
_clearOnFree = ShouldClear(clearMode);
|
|
_array = _pool.Rent(span.Length);
|
|
span.CopyTo(_array);
|
|
_size = span.Length;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// The number of items in the stack.
|
|
/// </summary>
|
|
public int Count => _size;
|
|
|
|
/// <summary>
|
|
/// Returns the ClearMode behavior for the collection, denoting whether values are
|
|
/// cleared from internal arrays before returning them to the pool.
|
|
/// </summary>
|
|
public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never;
|
|
|
|
bool ICollection.IsSynchronized => false;
|
|
|
|
object ICollection.SyncRoot
|
|
{
|
|
get
|
|
{
|
|
if (_syncRoot == null)
|
|
{
|
|
Interlocked.CompareExchange<object?>(ref _syncRoot, new object(), null);
|
|
}
|
|
return _syncRoot;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all Objects from the Stack.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
if (_clearOnFree)
|
|
{
|
|
Array.Clear(_array, 0, _size); // clear the elements so that the gc can reclaim the references.
|
|
}
|
|
_size = 0;
|
|
_version++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares items using the default equality comparer
|
|
/// </summary>
|
|
public bool Contains(T item)
|
|
{
|
|
// PERF: Internally Array.LastIndexOf calls
|
|
// EqualityComparer<T>.Default.LastIndexOf, which
|
|
// is specialized for different types. This
|
|
// boosts performance since instead of making a
|
|
// virtual method call each iteration of the loop,
|
|
// via EqualityComparer<T>.Default.Equals, we
|
|
// only make one virtual call to EqualityComparer.LastIndexOf.
|
|
|
|
return _size != 0 && Array.LastIndexOf(_array, item, _size - 1) != -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method removes all items which match the predicate.
|
|
/// The complexity is O(n).
|
|
/// </summary>
|
|
public int RemoveWhere(Func<T, bool> match)
|
|
{
|
|
if (match == null)
|
|
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
|
|
|
|
int freeIndex = 0; // the first free slot in items array
|
|
|
|
// Find the first item which needs to be removed.
|
|
while (freeIndex < _size && !match(_array[freeIndex]))
|
|
freeIndex++;
|
|
if (freeIndex >= _size)
|
|
return 0;
|
|
|
|
int current = freeIndex + 1;
|
|
while (current < _size)
|
|
{
|
|
// Find the first item which needs to be kept.
|
|
while (current < _size && match(_array[current]))
|
|
current++;
|
|
|
|
if (current < _size)
|
|
{
|
|
// copy item to the free slot.
|
|
_array[freeIndex++] = _array[current++];
|
|
}
|
|
}
|
|
|
|
if (_clearOnFree)
|
|
{
|
|
// Clear the removed elements so that the gc can reclaim the references.
|
|
Array.Clear(_array, freeIndex, _size - freeIndex);
|
|
}
|
|
|
|
int result = _size - freeIndex;
|
|
_size = freeIndex;
|
|
_version++;
|
|
return result;
|
|
}
|
|
|
|
// Copies the stack into an array.
|
|
public void CopyTo(T[] array, int arrayIndex)
|
|
{
|
|
if (array == null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
|
|
}
|
|
|
|
if (arrayIndex < 0 || arrayIndex > array.Length)
|
|
{
|
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
|
|
}
|
|
|
|
if (array.Length - arrayIndex < _size)
|
|
{
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
|
|
}
|
|
|
|
Debug.Assert(array != _array);
|
|
int srcIndex = 0;
|
|
int dstIndex = arrayIndex + _size;
|
|
while (srcIndex < _size)
|
|
{
|
|
array[--dstIndex] = _array[srcIndex++];
|
|
}
|
|
}
|
|
|
|
public void CopyTo(Span<T> span)
|
|
{
|
|
if (span.Length < _size)
|
|
{
|
|
ThrowHelper.ThrowArgumentException_DestinationTooShort();
|
|
}
|
|
|
|
int srcIndex = 0;
|
|
int dstIndex = _size;
|
|
while (srcIndex < _size)
|
|
{
|
|
span[--dstIndex] = _array[srcIndex++];
|
|
}
|
|
}
|
|
|
|
void ICollection.CopyTo(Array array, int arrayIndex)
|
|
{
|
|
if (array == null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
|
|
}
|
|
|
|
if (array.Rank != 1)
|
|
{
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
|
|
}
|
|
|
|
if (array.GetLowerBound(0) != 0)
|
|
{
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound, ExceptionArgument.array);
|
|
}
|
|
|
|
if (arrayIndex < 0 || arrayIndex > array.Length)
|
|
{
|
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
|
|
}
|
|
|
|
if (array.Length - arrayIndex < _size)
|
|
{
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
|
|
}
|
|
|
|
try
|
|
{
|
|
Array.Copy(_array, 0, array, arrayIndex, _size);
|
|
Array.Reverse(array, arrayIndex, _size);
|
|
}
|
|
catch (ArrayTypeMismatchException)
|
|
{
|
|
ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an IEnumerator for this PooledStack.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Enumerator GetEnumerator()
|
|
=> new Enumerator(this);
|
|
|
|
/// <internalonly/>
|
|
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
|
=> new Enumerator(this);
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
=> new Enumerator(this);
|
|
|
|
public void TrimExcess()
|
|
{
|
|
if (_size == 0)
|
|
{
|
|
ReturnArray(replaceWith: Array.Empty<T>());
|
|
_version++;
|
|
return;
|
|
}
|
|
|
|
int threshold = (int)(_array.Length * 0.9);
|
|
if (_size < threshold)
|
|
{
|
|
var newArray = _pool.Rent(_size);
|
|
if (newArray.Length < _array.Length)
|
|
{
|
|
Array.Copy(_array, newArray, _size);
|
|
ReturnArray(replaceWith: newArray);
|
|
_version++;
|
|
}
|
|
else
|
|
{
|
|
// The array from the pool wasn't any smaller than the one we already had,
|
|
// (we can only control minimum size) so return it and do nothing.
|
|
// If we create an exact-sized array not from the pool, we'll
|
|
// get an exception when returning it to the pool.
|
|
_pool.Return(newArray);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the top object on the stack without removing it. If the stack
|
|
/// is empty, Peek throws an InvalidOperationException.
|
|
/// </summary>
|
|
public T Peek()
|
|
{
|
|
int size = _size - 1;
|
|
T[] array = _array;
|
|
|
|
if ((uint)size >= (uint)array.Length)
|
|
{
|
|
ThrowForEmptyStack();
|
|
}
|
|
|
|
return array[size];
|
|
}
|
|
|
|
public bool TryPeek([MaybeNullWhen(false)] out T result)
|
|
{
|
|
int size = _size - 1;
|
|
T[] array = _array;
|
|
|
|
if ((uint)size >= (uint)array.Length)
|
|
{
|
|
result = default;
|
|
return false;
|
|
}
|
|
result = array[size];
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pops an item from the top of the stack. If the stack is empty, Pop
|
|
/// throws an InvalidOperationException.
|
|
/// </summary>
|
|
public T Pop()
|
|
{
|
|
int size = _size - 1;
|
|
T[] array = _array;
|
|
|
|
// if (_size == 0) is equivalent to if (size == -1), and this case
|
|
// is covered with (uint)size, thus allowing bounds check elimination
|
|
// https://github.com/dotnet/coreclr/pull/9773
|
|
if ((uint)size >= (uint)array.Length)
|
|
{
|
|
ThrowForEmptyStack();
|
|
}
|
|
|
|
_version++;
|
|
_size = size;
|
|
T item = array[size];
|
|
if (_clearOnFree)
|
|
{
|
|
array[size] = default!; // Free memory quicker.
|
|
}
|
|
return item;
|
|
}
|
|
|
|
public bool TryPop([MaybeNullWhen(false)] out T result)
|
|
{
|
|
int size = _size - 1;
|
|
T[] array = _array;
|
|
|
|
if ((uint)size >= (uint)array.Length)
|
|
{
|
|
result = default;
|
|
return false;
|
|
}
|
|
|
|
_version++;
|
|
_size = size;
|
|
result = array[size];
|
|
if (_clearOnFree)
|
|
{
|
|
array[size] = default!; // Free memory quicker.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pushes an item to the top of the stack.
|
|
/// </summary>
|
|
public void Push(T item)
|
|
{
|
|
int size = _size;
|
|
T[] array = _array;
|
|
|
|
if ((uint)size < (uint)array.Length)
|
|
{
|
|
array[size] = item;
|
|
_version++;
|
|
_size = size + 1;
|
|
}
|
|
else
|
|
{
|
|
PushWithResize(item);
|
|
}
|
|
}
|
|
|
|
// Non-inline from Stack.Push to improve its code quality as uncommon path
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private void PushWithResize(T item)
|
|
{
|
|
var newArray = _pool.Rent((_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
|
|
Array.Copy(_array, newArray, _size);
|
|
ReturnArray(replaceWith: newArray);
|
|
_array[_size] = item;
|
|
_version++;
|
|
_size++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the Stack to an array, in the same order Pop would return the items.
|
|
/// </summary>
|
|
public T[] ToArray()
|
|
{
|
|
if (_size == 0)
|
|
return Array.Empty<T>();
|
|
|
|
T[] objArray = new T[_size];
|
|
int i = 0;
|
|
while (i < _size)
|
|
{
|
|
objArray[i] = _array[_size - i - 1];
|
|
i++;
|
|
}
|
|
return objArray;
|
|
}
|
|
|
|
private void ThrowForEmptyStack()
|
|
{
|
|
Debug.Assert(_size == 0);
|
|
throw new InvalidOperationException("Stack was empty.");
|
|
}
|
|
|
|
private void ReturnArray(T[]? replaceWith = null)
|
|
{
|
|
if (_array?.Length > 0)
|
|
{
|
|
try
|
|
{
|
|
_pool.Return(_array, clearArray: _clearOnFree);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
// oh well, the array pool didn't like our array
|
|
}
|
|
}
|
|
|
|
if (!(replaceWith is null))
|
|
{
|
|
_array = replaceWith;
|
|
}
|
|
}
|
|
|
|
private static bool ShouldClear(ClearMode mode)
|
|
{
|
|
#if NETCOREAPP2_1
|
|
return mode == ClearMode.Always
|
|
|| (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
|
#else
|
|
return mode != ClearMode.Never;
|
|
#endif
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
ReturnArray(replaceWith: Array.Empty<T>());
|
|
_size = 0;
|
|
_version++;
|
|
}
|
|
|
|
void IDeserializationCallback.OnDeserialization(object? sender)
|
|
{
|
|
// We can't serialize array pools, so deserialized PooledStacks will
|
|
// have to use the shared pool, even if they were using a custom pool
|
|
// before serialization.
|
|
_pool = ArrayPool<T>.Shared;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")]
|
|
public struct Enumerator : IEnumerator<T>, IEnumerator
|
|
{
|
|
private readonly PooledStack<T> _stack;
|
|
private readonly int _version;
|
|
private int _index;
|
|
private T? _currentElement;
|
|
|
|
internal Enumerator(PooledStack<T> stack)
|
|
{
|
|
_stack = stack;
|
|
_version = stack._version;
|
|
_index = -2;
|
|
_currentElement = default;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_index = -1;
|
|
}
|
|
|
|
public bool MoveNext()
|
|
{
|
|
bool retval;
|
|
if (_version != _stack._version)
|
|
throw new InvalidOperationException("Collection was modified during enumeration.");
|
|
if (_index == -2)
|
|
{ // First call to enumerator.
|
|
_index = _stack._size - 1;
|
|
retval = (_index >= 0);
|
|
if (retval)
|
|
_currentElement = _stack._array[_index];
|
|
return retval;
|
|
}
|
|
if (_index == -1)
|
|
{ // End of enumeration.
|
|
return false;
|
|
}
|
|
|
|
retval = (--_index >= 0);
|
|
if (retval)
|
|
_currentElement = _stack._array[_index];
|
|
else
|
|
_currentElement = default;
|
|
return retval;
|
|
}
|
|
|
|
public T Current
|
|
{
|
|
get
|
|
{
|
|
if (_index < 0)
|
|
ThrowEnumerationNotStartedOrEnded();
|
|
return _currentElement!;
|
|
}
|
|
}
|
|
|
|
private void ThrowEnumerationNotStartedOrEnded()
|
|
{
|
|
Debug.Assert(_index == -1 || _index == -2);
|
|
throw new InvalidOperationException(_index == -2 ? "Enumeration was not started." : "Enumeration has ended.");
|
|
}
|
|
|
|
object? IEnumerator.Current
|
|
{
|
|
get { return Current; }
|
|
}
|
|
|
|
void IEnumerator.Reset()
|
|
{
|
|
if (_version != _stack._version)
|
|
throw new InvalidOperationException("Collection was modified during enumeration.");
|
|
_index = -2;
|
|
_currentElement = default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|