|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.ComponentModel;
namespace System.Collections.Immutable
{
public sealed partial class ImmutableSortedSet<T>
{
/// <summary>
/// Enumerates the contents of a binary tree.
/// </summary>
/// <remarks>
/// This struct can and should be kept in exact sync with the other binary tree enumerators:
/// <see cref="ImmutableList{T}.Enumerator"/>, <see cref="ImmutableSortedDictionary{TKey, TValue}.Enumerator"/>, and <see cref="ImmutableSortedSet{T}.Enumerator"/>.
///
/// CAUTION: when this enumerator is actually used as a valuetype (not boxed) do NOT copy it by assigning to a second variable
/// or by passing it to another method. When this enumerator is disposed of it returns a mutable reference type stack to a resource pool,
/// and if the value type enumerator is copied (which can easily happen unintentionally if you pass the value around) there is a risk
/// that a stack that has already been returned to the resource pool may still be in use by one of the enumerator copies, leading to data
/// corruption and/or exceptions.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public struct Enumerator : IEnumerator<T>, ISecurePooledObjectUser, IStrongEnumerator<T>
{
/// <summary>
/// The builder being enumerated, if applicable.
/// </summary>
private readonly Builder? _builder;
/// <summary>
/// A unique ID for this instance of this enumerator.
/// Used to protect pooled objects from use after they are recycled.
/// </summary>
private readonly int _poolUserId;
/// <summary>
/// A flag indicating whether this enumerator works in reverse sort order.
/// </summary>
private readonly bool _reverse;
/// <summary>
/// The set being enumerated.
/// </summary>
private Node _root;
/// <summary>
/// The stack to use for enumerating the binary tree.
/// </summary>
/// <remarks>
/// We use <see cref="RefAsValueType{T}"/> as a wrapper to avoid paying the cost of covariant checks whenever
/// the underlying array that the <see cref="Stack{T}"/> class uses is written to.
/// We've recognized this as a perf win in ETL traces for these stack frames:
/// clr!JIT_Stelem_Ref
/// clr!ArrayStoreCheck
/// clr!ObjIsInstanceOf
/// </remarks>
private SecurePooledObject<Stack<RefAsValueType<Node>>>? _stack;
/// <summary>
/// The node currently selected.
/// </summary>
private Node? _current;
/// <summary>
/// The version of the builder (when applicable) that is being enumerated.
/// </summary>
private int _enumeratingBuilderVersion;
/// <summary>
/// Initializes an <see cref="Enumerator"/> structure.
/// </summary>
/// <param name="root">The root of the set to be enumerated.</param>
/// <param name="builder">The builder, if applicable.</param>
/// <param name="reverse"><c>true</c> to enumerate the collection in reverse.</param>
internal Enumerator(Node root, Builder? builder = null, bool reverse = false)
{
Requires.NotNull(root, nameof(root));
_root = root;
_builder = builder;
_current = null;
_reverse = reverse;
_enumeratingBuilderVersion = builder != null ? builder.Version : -1;
_poolUserId = SecureObjectPool.NewId();
_stack = null;
if (!SecureObjectPool<Stack<RefAsValueType<Node>>, Enumerator>.TryTake(this, out _stack))
{
_stack = SecureObjectPool<Stack<RefAsValueType<Node>>, Enumerator>.PrepNew(this, new Stack<RefAsValueType<Node>>(root.Height));
}
this.PushNext(_root);
}
/// <inheritdoc/>
int ISecurePooledObjectUser.PoolUserId
{
get { return _poolUserId; }
}
/// <summary>
/// The current element.
/// </summary>
public T Current
{
get
{
this.ThrowIfDisposed();
if (_current != null)
{
return _current.Value;
}
throw new InvalidOperationException();
}
}
/// <summary>
/// The current element.
/// </summary>
object? System.Collections.IEnumerator.Current
{
get { return this.Current; }
}
/// <summary>
/// Disposes of this enumerator and returns the stack reference to the resource pool.
/// </summary>
public void Dispose()
{
_root = null!;
_current = null;
if (_stack != null && _stack.TryUse(ref this, out Stack<RefAsValueType<Node>>? stack))
{
stack.ClearFastWhenEmpty();
SecureObjectPool<Stack<RefAsValueType<Node>>, Enumerator>.TryAdd(this, _stack!);
_stack = null;
}
}
/// <summary>
/// Advances enumeration to the next element.
/// </summary>
/// <returns>A value indicating whether there is another element in the enumeration.</returns>
public bool MoveNext()
{
this.ThrowIfDisposed();
this.ThrowIfChanged();
Stack<RefAsValueType<ImmutableSortedSet<T>.Node>> stack = _stack!.Use(ref this);
if (stack.Count > 0)
{
Node n = stack.Pop().Value;
_current = n;
this.PushNext(_reverse ? n.Left! : n.Right!);
return true;
}
else
{
_current = null;
return false;
}
}
/// <summary>
/// Restarts enumeration.
/// </summary>
public void Reset()
{
this.ThrowIfDisposed();
_enumeratingBuilderVersion = _builder != null ? _builder.Version : -1;
_current = null;
Stack<RefAsValueType<ImmutableSortedSet<T>.Node>> stack = _stack!.Use(ref this);
stack.ClearFastWhenEmpty();
this.PushNext(_root);
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> if this enumerator has been disposed.
/// </summary>
private void ThrowIfDisposed()
{
// Since this is a struct, copies might not have been marked as disposed.
// But the stack we share across those copies would know.
// This trick only works when we have a non-null stack.
// For enumerators of empty collections, there isn't any natural
// way to know when a copy of the struct has been disposed of.
if (_root == null || (_stack != null && !_stack.IsOwned(ref this)))
{
Requires.FailObjectDisposed(this);
}
}
/// <summary>
/// Throws an exception if the underlying builder's contents have been changed since enumeration started.
/// </summary>
/// <exception cref="System.InvalidOperationException">Thrown if the collection has changed.</exception>
private void ThrowIfChanged()
{
if (_builder != null && _builder.Version != _enumeratingBuilderVersion)
{
throw new InvalidOperationException(SR.CollectionModifiedDuringEnumeration);
}
}
/// <summary>
/// Pushes this node and all its Left (or Right, if reversed) descendants onto the stack.
/// </summary>
/// <param name="node">The starting node to push onto the stack.</param>
private void PushNext(Node node)
{
Requires.NotNull(node, nameof(node));
Stack<RefAsValueType<ImmutableSortedSet<T>.Node>> stack = _stack!.Use(ref this);
while (!node.IsEmpty)
{
stack.Push(new RefAsValueType<Node>(node));
node = _reverse ? node.Right! : node.Left!;
}
}
}
}
}
|