Skip to content

Commit a110fed

Browse files
authored
Remove custom enumerator from ConcurrentBag (#117353)
ConcurrentBag's snapshot enumeration semantics are obtained by creating an array of the contents and then enumerating that array. This was done with a custom enumerator out of an abundance of caution about maintaining semantics for operations with undefined behavior, e.g. using Current after MoveNext returns false. We've since stopped trying to maintain bug-for-bug compatibility in such undefined areas. As such, this just deletes the custom enumerator and uses Array's. This not only reduces code but has better performance characteristics (though the whole ToArray as part of enumeration already dominates the enumeration costs).
1 parent 2583ec8 commit a110fed

File tree

2 files changed

+3
-57
lines changed

2 files changed

+3
-57
lines changed

src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentBag.cs

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ public void Clear()
459459
/// <see cref="GetEnumerator"/> was called. The enumerator is safe to use
460460
/// concurrently with reads from and writes to the bag.
461461
/// </remarks>
462-
public IEnumerator<T> GetEnumerator() => new Enumerator(ToArray());
462+
public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)ToArray()).GetEnumerator();
463463

464464
/// <summary>
465465
/// Returns an enumerator that iterates through the <see
@@ -1074,61 +1074,5 @@ internal enum Operation
10741074
Add,
10751075
Take
10761076
};
1077-
1078-
/// <summary>Provides an enumerator for the bag.</summary>
1079-
/// <remarks>
1080-
/// The original implementation of ConcurrentBag used a <see cref="List{T}"/> as part of
1081-
/// the GetEnumerator implementation. That list was then changed to be an array, but array's
1082-
/// GetEnumerator has different behavior than does list's, in particular for the case where
1083-
/// Current is used after MoveNext returns false. To avoid any concerns around compatibility,
1084-
/// we use a custom enumerator rather than just returning array's. This enumerator provides
1085-
/// the essential elements of both list's and array's enumerators.
1086-
/// </remarks>
1087-
private sealed class Enumerator : IEnumerator<T>
1088-
{
1089-
private readonly T[] _array;
1090-
private T? _current;
1091-
private int _index;
1092-
1093-
public Enumerator(T[] array)
1094-
{
1095-
Debug.Assert(array != null);
1096-
_array = array;
1097-
}
1098-
1099-
public bool MoveNext()
1100-
{
1101-
if (_index < _array.Length)
1102-
{
1103-
_current = _array[_index++];
1104-
return true;
1105-
}
1106-
1107-
_index = _array.Length + 1;
1108-
return false;
1109-
}
1110-
1111-
public T Current => _current!;
1112-
1113-
object? IEnumerator.Current
1114-
{
1115-
get
1116-
{
1117-
if (_index == 0 || _index == _array.Length + 1)
1118-
{
1119-
throw new InvalidOperationException(SR.ConcurrentBag_Enumerator_EnumerationNotStartedOrAlreadyFinished);
1120-
}
1121-
return Current;
1122-
}
1123-
}
1124-
1125-
public void Reset()
1126-
{
1127-
_index = 0;
1128-
_current = default;
1129-
}
1130-
1131-
public void Dispose() { }
1132-
}
11331077
}
11341078
}

src/libraries/System.Collections.Concurrent/tests/ConcurrentBagTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace System.Collections.Concurrent.Tests
1111
{
1212
public class ConcurrentBagTests : ProducerConsumerCollectionTests
1313
{
14+
protected override bool Enumerator_Current_UndefinedOperation_Throws => true;
15+
protected override bool Enumerator_Empty_UsesSingletonInstance => true;
1416
protected override IProducerConsumerCollection<T> CreateProducerConsumerCollection<T>() => new ConcurrentBag<T>();
1517
protected override IProducerConsumerCollection<int> CreateProducerConsumerCollection(IEnumerable<int> collection) => new ConcurrentBag<int>(collection);
1618
protected override bool IsEmpty(IProducerConsumerCollection<int> pcc) => ((ConcurrentBag<int>)pcc).IsEmpty;

0 commit comments

Comments
 (0)