/
ImmutableSortedSet_1.Enumerator.cs
225 lines (201 loc) · 9.01 KB
/
ImmutableSortedSet_1.Enumerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// 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!;
}
}
}
}
}