From dc6ad37d410931edb6b46203fb73d6503ed29dc8 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 23 Feb 2023 18:00:59 -0500 Subject: [PATCH] Return singleton enumerators from IEnumerable.GetEnumerator for empty collections (#82499) * Return singleton enumerators from IEnumerable.GetEnumerator for empty collections Change the `IEnumerable.GetEnumerator()` implementations on our core collection types to special-case Count==0 in order to return a single enumerator instead of allocating one a new each time. This saves an allocation when enumerating these collections via the interface in exchange for an extra length check as part of GetEnumerator. * Address PR feedback - Create helper function for empty enumerator - Add tests for singletons * Fix a few tests --- .../Collections/Generic/EnumerableHelpers.cs | 4 + .../Collections/IDictionary.Generic.Tests.cs | 4 +- .../IDictionary.NonGeneric.Tests.cs | 4 +- .../Collections/IEnumerable.Generic.Tests.cs | 58 +++++++-- .../IEnumerable.NonGeneric.Tests.cs | 119 +++++++++++++++++- .../System/Collections/IList.Generic.Tests.cs | 4 +- .../ConcurrentDictionary.Generic.Tests.cs | 1 + .../ConcurrentDictionary.NonGeneric.Tests.cs | 2 + .../ImmutableArray.Generic.Tests.cs | 1 + .../ImmutableArray.NonGeneric.Tests.cs | 1 + .../System/Collections/Generic/LinkedList.cs | 17 +-- .../Collections/Generic/PriorityQueue.cs | 6 +- .../Collections/Generic/SortedDictionary.cs | 56 +++------ .../System/Collections/Generic/SortedList.cs | 46 +++---- .../System/Collections/Generic/SortedSet.cs | 2 +- .../src/System/Collections/Generic/Stack.cs | 17 +-- .../Dictionary.Generic.Tests.Keys.cs | 3 + .../Dictionary.Generic.Tests.Values.cs | 5 + .../Dictionary/Dictionary.Generic.Tests.cs | 4 + .../Generic/Dictionary/Dictionary.Tests.cs | 3 + ...t.Generic.Tests.AsNonGenericIEnumerable.cs | 1 + .../Generic/HashSet/HashSet.Generic.Tests.cs | 2 + ...t.Generic.Tests.AsNonGenericICollection.cs | 3 + .../LinkedList/LinkedList.Generic.Tests.cs | 3 + .../tests/Generic/List/List.Generic.Tests.cs | 3 + .../Generic/Queue/Queue.Generic.Tests.cs | 3 +- .../tests/Generic/Queue/Queue.Tests.cs | 2 + .../SortedDictionary.Generic.Tests.Keys.cs | 3 + .../SortedDictionary.Generic.Tests.Values.cs | 3 + .../SortedDictionary.Generic.Tests.cs | 4 +- .../SortedDictionary.Tests.cs | 2 + .../SortedList.Generic.Tests.Keys.cs | 4 + .../SortedList.Generic.Tests.Values.cs | 4 + .../SortedList/SortedList.Generic.Tests.cs | 3 + .../Generic/SortedList/SortedList.Tests.cs | 3 +- .../Generic/Stack/Stack.Generic.Tests.cs | 3 + .../tests/Generic/Stack/Stack.Tests.cs | 2 + .../src/System/ArraySegment.cs | 10 +- .../System/Collections/Generic/Dictionary.cs | 17 ++- .../src/System/Collections/Generic/HashSet.cs | 6 +- .../src/System/Collections/Generic/List.cs | 13 +- .../src/System/Collections/Generic/Queue.cs | 17 +-- .../ObjectModel/ReadOnlyCollection.cs | 5 +- .../CompilerServices/ConditionalWeakTable.cs | 2 +- .../tests/System/ArraySegmentTests.cs | 1 + 45 files changed, 319 insertions(+), 157 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs index b8646e3e878a5d..f73a2d3193f32f 100644 --- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs +++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs @@ -8,6 +8,10 @@ namespace System.Collections.Generic /// internal static partial class EnumerableHelpers { + /// Gets an enumerator singleton for an empty collection. + internal static IEnumerator GetEmptyEnumerator() => + ((IEnumerable)Array.Empty()).GetEnumerator(); + /// Converts an enumerable to an array using the same logic as List{T}. /// The enumerable to convert. /// The number of items stored in the resulting array, 0-indexed. diff --git a/src/libraries/Common/tests/System/Collections/IDictionary.Generic.Tests.cs b/src/libraries/Common/tests/System/Collections/IDictionary.Generic.Tests.cs index 44e28ea3ecd8c0..22f3912b65e7dd 100644 --- a/src/libraries/Common/tests/System/Collections/IDictionary.Generic.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IDictionary.Generic.Tests.cs @@ -421,7 +421,7 @@ public void IDictionary_Generic_Keys_Enumeration_ParentDictionaryModifiedInvalid ICollection keys = dictionary.Keys; IEnumerator keysEnum = keys.GetEnumerator(); dictionary.Add(GetNewKey(dictionary), CreateTValue(3432)); - if (IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified) { Assert.Throws(() => keysEnum.MoveNext()); Assert.Throws(() => keysEnum.Reset()); @@ -528,7 +528,7 @@ public void IDictionary_Generic_Values_Enumeration_ParentDictionaryModifiedInval ICollection values = dictionary.Values; IEnumerator valuesEnum = values.GetEnumerator(); dictionary.Add(GetNewKey(dictionary), CreateTValue(3432)); - if (IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified) { Assert.Throws(() => valuesEnum.MoveNext()); Assert.Throws(() => valuesEnum.Reset()); diff --git a/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs b/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs index 944d067300fb65..adca2780d96d5a 100644 --- a/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IDictionary.NonGeneric.Tests.cs @@ -392,7 +392,7 @@ public void IDictionary_NonGeneric_Keys_Enumeration_ParentDictionaryModifiedInva ICollection keys = dictionary.Keys; IEnumerator keysEnum = keys.GetEnumerator(); dictionary.Add(GetNewKey(dictionary), CreateTValue(3432)); - if (IDictionary_NonGeneric_Keys_Values_ParentDictionaryModifiedInvalidates) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : IDictionary_NonGeneric_Keys_Values_ParentDictionaryModifiedInvalidates) { Assert.Throws(() => keysEnum.MoveNext()); Assert.Throws(() => keysEnum.Reset()); @@ -487,7 +487,7 @@ public virtual void IDictionary_NonGeneric_Values_Enumeration_ParentDictionaryMo ICollection values = dictionary.Values; IEnumerator valuesEnum = values.GetEnumerator(); dictionary.Add(GetNewKey(dictionary), CreateTValue(3432)); - if (IDictionary_NonGeneric_Keys_Values_ParentDictionaryModifiedInvalidates) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : IDictionary_NonGeneric_Keys_Values_ParentDictionaryModifiedInvalidates) { Assert.Throws(() => valuesEnum.MoveNext()); Assert.Throws(() => valuesEnum.Reset()); diff --git a/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs b/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs index c23f9ca2542478..a71f72e6cc6fa9 100644 --- a/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs @@ -85,6 +85,20 @@ public abstract partial class IEnumerable_Generic_Tests : TestBase /// protected virtual bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => true; + /// + /// When calling MoveNext or Reset after modification of an empty enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// . + /// + protected virtual bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException; + + /// Whether the enumerator returned from GetEnumerator is a singleton instance when the collection is empty. + protected virtual bool Enumerator_Empty_UsesSingletonInstance => false; + /// /// Specifies whether this IEnumerable follows some sort of ordering pattern. /// @@ -302,6 +316,30 @@ private void VerifyEnumerator( #region GetEnumerator() + [Fact] + public void IEnumerable_NonGeneric_GetEnumerator_EmptyCollection_UsesSingleton() + { + IEnumerable enumerable = GenericIEnumerableFactory(0); + + IEnumerator enumerator1 = enumerable.GetEnumerator(); + try + { + IEnumerator enumerator2 = enumerable.GetEnumerator(); + try + { + Assert.Equal(Enumerator_Empty_UsesSingletonInstance, ReferenceEquals(enumerator1, enumerator2)); + } + finally + { + if (enumerator2 is IDisposable d2) d2.Dispose(); + } + } + finally + { + if (enumerator1 is IDisposable d1) d1.Dispose(); + } + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void IEnumerable_Generic_GetEnumerator_NoExceptionsWhileGetting(int count) @@ -381,7 +419,7 @@ public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedBeforeEnumeration_Th { if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.MoveNext()); } @@ -427,7 +465,7 @@ public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedDuringEnumeration_Th enumerator.MoveNext(); if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.MoveNext()); } @@ -471,7 +509,7 @@ public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedAfterEnumeration_Thr while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.MoveNext()); } @@ -602,8 +640,7 @@ public void IEnumerable_Generic_Enumerator_Current_BeforeFirstMoveNext_Undefined IEnumerable enumerable = GenericIEnumerableFactory(count); using (IEnumerator enumerator = enumerable.GetEnumerator()) { - if (Enumerator_Current_UndefinedOperation_Throws || - (count == 0 && Enumerator_Empty_Current_UndefinedOperation_Throws)) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) Assert.Throws(() => enumerator.Current); else current = enumerator.Current; @@ -619,8 +656,7 @@ public void IEnumerable_Generic_Enumerator_Current_AfterEndOfEnumerable_Undefine using (IEnumerator enumerator = enumerable.GetEnumerator()) { while (enumerator.MoveNext()) ; - if (Enumerator_Current_UndefinedOperation_Throws || - (count == 0 && Enumerator_Empty_Current_UndefinedOperation_Throws)) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) Assert.Throws(() => enumerator.Current); else current = enumerator.Current; @@ -639,7 +675,7 @@ public void IEnumerable_Generic_Enumerator_Current_ModifiedDuringEnumeration_Und { if (ModifyEnumerable(enumerable)) { - if (Enumerator_Current_UndefinedOperation_Throws) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) Assert.Throws(() => enumerator.Current); else current = enumerator.Current; @@ -694,7 +730,7 @@ public void IEnumerable_Generic_Enumerator_Reset_ModifiedBeforeEnumeration_Throw { if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.Reset()); } @@ -737,7 +773,7 @@ public void IEnumerable_Generic_Enumerator_Reset_ModifiedDuringEnumeration_Throw enumerator.MoveNext(); if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.Reset()); } @@ -781,7 +817,7 @@ public void IEnumerable_Generic_Enumerator_Reset_ModifiedAfterEnumeration_Throws while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.Reset()); } diff --git a/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs b/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs index 5760cbef9069c1..a441cf4d787330 100644 --- a/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs @@ -60,6 +60,31 @@ public abstract partial class IEnumerable_NonGeneric_Tests : TestBase /// protected virtual bool Enumerator_Current_UndefinedOperation_Throws => false; + /// + /// When calling MoveNext or Reset after modification of the enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// true. + /// + protected virtual bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => true; + + /// + /// When calling MoveNext or Reset after modification of an empty enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// . + /// + protected virtual bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException; + + /// Whether the enumerator returned from GetEnumerator is a singleton instance when the collection is empty. + protected virtual bool Enumerator_Empty_UsesSingletonInstance => false; + /// /// Whether the collection can be serialized. /// @@ -83,6 +108,30 @@ protected enum EnumerableOrder #region GetEnumerator() + [Fact] + public void IEnumerable_NonGeneric_GetEnumerator_EmptyCollection_UsesSingleton() + { + IEnumerable enumerable = NonGenericIEnumerableFactory(0); + + IEnumerator enumerator1 = enumerable.GetEnumerator(); + try + { + IEnumerator enumerator2 = enumerable.GetEnumerator(); + try + { + Assert.Equal(Enumerator_Empty_UsesSingletonInstance, ReferenceEquals(enumerator1, enumerator2)); + } + finally + { + if (enumerator2 is IDisposable d2) d2.Dispose(); + } + } + finally + { + if (enumerator1 is IDisposable d1) d1.Dispose(); + } + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void IEnumerable_NonGeneric_GetEnumerator_NoExceptionsWhileGetting(int count) @@ -141,7 +190,16 @@ public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedBeforeEnumeration IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.MoveNext()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + _ = enumerator.MoveNext(); + } + } }); } @@ -153,10 +211,21 @@ public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedDuringEnumeration { IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); + for (int i = 0; i < count / 2; i++) enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.MoveNext()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + enumerator.MoveNext(); + } + } }); } @@ -170,7 +239,16 @@ public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedAfterEnumeration_ IEnumerator enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.MoveNext()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + _ = enumerator.MoveNext(); + } + } }); } @@ -290,7 +368,16 @@ public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedBeforeEnumeration_Th IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.Reset()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } }); } @@ -302,10 +389,21 @@ public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedDuringEnumeration_Th { IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); + for (int i = 0; i < count / 2; i++) enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.Reset()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } }); } @@ -319,7 +417,16 @@ public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedAfterEnumeration_Thr IEnumerator enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.Reset()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } }); } diff --git a/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs b/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs index 820ea6407ab755..44f67e0ca24f83 100644 --- a/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs @@ -614,7 +614,7 @@ public void IList_Generic_CurrentAtEnd_AfterAdd(int count) while (enumerator.MoveNext()) ; // Go to end of enumerator T current = default(T); - if (Enumerator_Current_UndefinedOperation_Throws) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) { Assert.Throws(() => enumerator.Current); // enumerator.Current should fail } @@ -630,7 +630,7 @@ public void IList_Generic_CurrentAtEnd_AfterAdd(int count) { collection.Add(CreateT(seed++)); - if (Enumerator_Current_UndefinedOperation_Throws) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) { Assert.Throws(() => enumerator.Current); // enumerator.Current should fail } diff --git a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.Generic.Tests.cs b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.Generic.Tests.cs index 1ec173dbc640db..f488444133a303 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.Generic.Tests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.Generic.Tests.cs @@ -189,6 +189,7 @@ public abstract class ConcurrentDictionary_Generic_Tests : IDictio protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override bool IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified => false; protected override bool IDictionary_Generic_Keys_Values_ModifyingTheDictionaryUpdatesTheCollection => false; diff --git a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.NonGeneric.Tests.cs b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.NonGeneric.Tests.cs index 9df2101c84a4e9..b7e63f51f402f9 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.NonGeneric.Tests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionary.NonGeneric.Tests.cs @@ -38,6 +38,8 @@ protected override object CreateTKey(int seed) protected override bool Enumerator_Current_UndefinedOperation_Throws => false; + protected override bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + protected override bool IDictionary_NonGeneric_Keys_Values_ModifyingTheDictionaryUpdatesTheCollection => false; protected override bool ICollection_NonGeneric_SupportsSyncRoot => false; diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.Generic.Tests.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.Generic.Tests.cs index f1373db05c6423..a21bcf815d4f67 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.Generic.Tests.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.Generic.Tests.cs @@ -20,6 +20,7 @@ public abstract class ImmutableArray_Generic_Tests : IList_Generic_Tests protected override bool IsReadOnly => true; protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); protected override Type IList_Generic_Item_InvalidIndex_ThrowType => typeof(IndexOutOfRangeException); + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override IList GenericIListFactory() => GenericIListFactory(0); diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.NonGeneric.Tests.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.NonGeneric.Tests.cs index 0d9410cedca93e..fabefa12d4cd47 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.NonGeneric.Tests.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArray/ImmutableArray.NonGeneric.Tests.cs @@ -15,6 +15,7 @@ public class ImmutableArray_NonGeneric_Tests : IList_NonGeneric_Tests protected override Type IList_NonGeneric_Item_InvalidIndex_ThrowType => typeof(IndexOutOfRangeException); protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override Type ICollection_NonGeneric_CopyTo_TwoDimensionArray_ThrowType => typeof(RankException); diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs b/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs index 0e99312eba2895..a50dce3b158946 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/LinkedList.cs @@ -283,15 +283,11 @@ public void CopyTo(T[] array, int index) return null; } - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } + public Enumerator GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + GetEnumerator(); public bool Remove(T value) { @@ -497,10 +493,7 @@ void ICollection.CopyTo(Array array, int index) } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); public struct Enumerator : IEnumerator, IEnumerator, ISerializable, IDeserializationCallback { diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs index 67f2ae3c176d13..e8d8221641cd2a 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs @@ -961,9 +961,11 @@ void IEnumerator.Reset() /// An for the . public Enumerator GetEnumerator() => new Enumerator(_queue); - IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.GetEnumerator() => GetEnumerator(); + IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.GetEnumerator() => + _queue.Count == 0 ? EnumerableHelpers.GetEmptyEnumerator<(TElement Element, TPriority Priority)>() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<(TElement Element, TPriority Priority)>)this).GetEnumerator(); } } } diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/SortedDictionary.cs b/src/libraries/System.Collections/src/System/Collections/Generic/SortedDictionary.cs index 08335221c4011a..64d745a778fcef 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/SortedDictionary.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/SortedDictionary.cs @@ -247,15 +247,11 @@ public void CopyTo(KeyValuePair[] array, int index) _set.CopyTo(array, index); } - public Enumerator GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + public Enumerator GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair); - IEnumerator> IEnumerable>.GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + IEnumerator> IEnumerable>.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator>() : + GetEnumerator(); public bool Remove(TKey key) { @@ -389,10 +385,7 @@ private static bool IsCompatibleKey(object key) return (key is TKey); } - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return new Enumerator(this, Enumerator.DictEntry); - } + IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, Enumerator.DictEntry); void IDictionary.Remove(object key) { @@ -412,10 +405,7 @@ object ICollection.SyncRoot get { return ((ICollection)_set).SyncRoot; } } - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); public struct Enumerator : IEnumerator>, IDictionaryEnumerator { @@ -541,20 +531,13 @@ public KeyCollection(SortedDictionary dictionary) _dictionary = dictionary; } - public Enumerator GetEnumerator() - { - return new Enumerator(_dictionary); - } + public Enumerator GetEnumerator() => new Enumerator(_dictionary); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); public void CopyTo(TKey[] array, int index) { @@ -709,20 +692,13 @@ public ValueCollection(SortedDictionary dictionary) _dictionary = dictionary; } - public Enumerator GetEnumerator() - { - return new Enumerator(_dictionary); - } + public Enumerator GetEnumerator() => new Enumerator(_dictionary); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_dictionary); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); public void CopyTo(TValue[] array, int index) { diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs b/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs index 3204094bf71ecd..167e1be161f027 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs @@ -542,25 +542,15 @@ public void SetValueAtIndex(int index, TValue value) version++; } - public IEnumerator> GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + public IEnumerator> GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair); - IEnumerator> IEnumerable>.GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + IEnumerator> IEnumerable>.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator>() : + GetEnumerator(); - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return new Enumerator(this, Enumerator.DictEntry); - } + IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, Enumerator.DictEntry); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this, Enumerator.KeyValuePair); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); /// /// Gets the key corresponding to the specified index. @@ -1091,15 +1081,11 @@ public TKey this[int index] } } - public IEnumerator GetEnumerator() - { - return new SortedListKeyEnumerator(_dict); - } + public IEnumerator GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + new SortedListKeyEnumerator(_dict); - IEnumerator IEnumerable.GetEnumerator() - { - return new SortedListKeyEnumerator(_dict); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int IndexOf(TKey key) { @@ -1209,15 +1195,11 @@ public TValue this[int index] } } - public IEnumerator GetEnumerator() - { - return new SortedListValueEnumerator(_dict); - } + public IEnumerator GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + new SortedListValueEnumerator(_dict); - IEnumerator IEnumerable.GetEnumerator() - { - return new SortedListValueEnumerator(_dict); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int IndexOf(TValue value) { diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs b/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs index 57f3ed24e596ef..3282996e5bc5d3 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs @@ -591,7 +591,7 @@ void ICollection.CopyTo(Array array, int index) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); #endregion diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs b/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs index ae5cc7bf0f93a7..2bc209c6a01e0b 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs @@ -152,21 +152,14 @@ void ICollection.CopyTo(Array array, int arrayIndex) } // Returns an IEnumerator for this Stack. - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } + public Enumerator GetEnumerator() => new Enumerator(this); /// - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? EnumerableHelpers.GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); public void TrimExcess() { diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Keys.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Keys.cs index f4386897680552..a71dad9a34d3c2 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Keys.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Keys.cs @@ -9,6 +9,8 @@ namespace System.Collections.Tests { public class Dictionary_Generic_Tests_Keys : ICollection_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; protected override bool DefaultValueAllowed => false; protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; @@ -61,6 +63,7 @@ public class Dictionary_Generic_Tests_Keys_AsICollection : ICollection_NonGeneri protected override bool NullAllowed => false; protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override Type ICollection_NonGeneric_CopyTo_ArrayOfEnumType_ThrowType => typeof(ArgumentException); protected override bool SupportsSerialization => false; diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Values.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Values.cs index 936705f8215b3f..efb7ef3b29ccb0 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Values.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.Values.cs @@ -13,6 +13,9 @@ public class Dictionary_Generic_Tests_Values : ICollection_Generic_Tests protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; protected override ICollection GenericICollectionFactory() { @@ -62,10 +65,12 @@ public class Dictionary_Generic_Tests_Values_AsICollection : ICollection_NonGene protected override bool NullAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override Type ICollection_NonGeneric_CopyTo_ArrayOfEnumType_ThrowType => typeof(ArgumentException); protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); protected override bool SupportsSerialization => false; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override Type ICollection_NonGeneric_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException); diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs index b42b1733f0742f..fa12ef55d6c540 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs @@ -14,6 +14,10 @@ namespace System.Collections.Tests /// public abstract class Dictionary_Generic_Tests : IDictionary_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + protected override ModifyOperation ModifyEnumeratorThrows => ModifyOperation.Add | ModifyOperation.Insert; protected override ModifyOperation ModifyEnumeratorAllowed => ModifyOperation.Overwrite | ModifyOperation.Remove | ModifyOperation.Clear; diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs index 31271dc03c217d..1a0845a3529865 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs @@ -12,6 +12,9 @@ namespace System.Collections.Tests { public partial class Dictionary_IDictionary_NonGeneric_Tests : IDictionary_NonGeneric_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + protected override IDictionary NonGenericIDictionaryFactory() { return new Dictionary(); diff --git a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs index ff8901ae2afed5..70671721d338af 100644 --- a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs +++ b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs @@ -16,6 +16,7 @@ protected override IEnumerable NonGenericIEnumerableFactory(int count) return set; } + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override ModifyOperation ModifyEnumeratorThrows => PlatformDetection.IsNetFramework ? base.ModifyEnumeratorThrows : (base.ModifyEnumeratorAllowed & ~ModifyOperation.Remove); diff --git a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs index 65261bfd26e422..c6aee132375a75 100644 --- a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs @@ -15,6 +15,8 @@ namespace System.Collections.Tests public abstract class HashSet_Generic_Tests : ISet_Generic_Tests { #region ISet Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; protected override bool ResetImplemented => true; diff --git a/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.AsNonGenericICollection.cs b/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.AsNonGenericICollection.cs index 1b4cf93cabc287..a3854d76ebda12 100644 --- a/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.AsNonGenericICollection.cs +++ b/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.AsNonGenericICollection.cs @@ -8,6 +8,9 @@ namespace System.Collections.Tests { public class LinkedList_ICollection_NonGeneric_Tests : ICollection_NonGeneric_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + protected override Type ICollection_NonGeneric_CopyTo_ArrayOfEnumType_ThrowType => typeof(ArgumentException); protected override void AddToCollection(ICollection collection, int numberOfItemsToAdd) diff --git a/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.cs index 9d656a84bd98c0..7f0998cac8789e 100644 --- a/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/LinkedList/LinkedList.Generic.Tests.cs @@ -12,6 +12,9 @@ namespace System.Collections.Tests public abstract partial class LinkedList_Generic_Tests : ICollection_Generic_Tests { #region ICollection Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override ICollection GenericICollectionFactory() { diff --git a/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.cs index 079acc7ade7d42..e10c12c48013c1 100644 --- a/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.cs @@ -12,6 +12,9 @@ namespace System.Collections.Tests public abstract partial class List_Generic_Tests : IList_Generic_Tests { #region IList Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override IList GenericIListFactory() { diff --git a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs index d38d98655b737c..3fd069023ca253 100644 --- a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs @@ -50,8 +50,9 @@ protected override IEnumerable GenericIEnumerableFactory(int count) protected override bool Contains(IEnumerable enumerable, T value) => ((Queue)enumerable).Contains(value); protected override void CopyTo(IEnumerable enumerable, T[] array, int index) => ((Queue)enumerable).CopyTo(array, index); protected override bool Remove(IEnumerable enumerable) => ((Queue)enumerable).TryDequeue(out _); + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; - + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override Type IGenericSharedAPI_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException); #endregion diff --git a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Tests.cs b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Tests.cs index 8f8afc317fcd1d..62b855d3f5dc74 100644 --- a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Tests.cs @@ -10,6 +10,8 @@ namespace System.Collections.Tests public class Queue_ICollection_NonGeneric_Tests : ICollection_NonGeneric_Tests { #region ICollection Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override Type ICollection_NonGeneric_CopyTo_ArrayOfEnumType_ThrowType => typeof(ArgumentException); diff --git a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs index 55d7e446de8706..db626df07701af 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Keys.cs @@ -9,6 +9,8 @@ namespace System.Collections.Tests { public class SortedDictionary_Generic_Tests_Keys : ICollection_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; protected override bool DefaultValueAllowed => false; protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; @@ -59,6 +61,7 @@ public class SortedDictionary_Generic_Tests_Keys_AsICollection : ICollection_Non protected override bool NullAllowed => false; protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); protected override ICollection NonGenericICollectionFactory() diff --git a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs index 3030fdaaa08205..fd4669f7707dd4 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.Values.cs @@ -9,6 +9,8 @@ namespace System.Collections.Tests { public class SortedDictionary_Generic_Tests_Values : ICollection_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; protected override bool DefaultValueAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; @@ -61,6 +63,7 @@ public class SortedDictionary_Generic_Tests_Values_AsICollection : ICollection_N protected override bool NullAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new List(); protected override bool SupportsSerialization => false; diff --git a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs index cac00bc6723b4d..830181188c5fa3 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Generic.Tests.cs @@ -13,7 +13,9 @@ namespace System.Collections.Tests public abstract class SortedDictionary_Generic_Tests : IDictionary_Generic_Tests { #region IDictionary Helper Methods - + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override bool DefaultValueWhenNotAllowed_Throws { get { return false; } } protected override IDictionary GenericIDictionaryFactory() diff --git a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Tests.cs b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Tests.cs index f83ed756cc7f19..7b393933029491 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedDictionary/SortedDictionary.Tests.cs @@ -9,6 +9,8 @@ namespace System.Collections.Tests public class SortedDictionary_IDictionary_NonGeneric_Tests : IDictionary_NonGeneric_Tests { #region IDictionary Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override IDictionary NonGenericIDictionaryFactory() { diff --git a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Keys.cs b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Keys.cs index 58e69384033d0d..4c5971cd359159 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Keys.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Keys.cs @@ -9,6 +9,9 @@ namespace System.Collections.Tests { public class SortedList_Generic_Tests_Keys : IList_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override bool DefaultValueAllowed => false; protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; @@ -57,6 +60,7 @@ public class SortedList_Generic_Tests_Keys_AsICollection : ICollection_NonGeneri protected override bool NullAllowed => false; protected override bool DuplicateValuesAllowed => false; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override bool SupportsSerialization => false; diff --git a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Values.cs b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Values.cs index 14eeaa0166b208..32ec7e651f3cbb 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Values.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.Values.cs @@ -9,6 +9,9 @@ namespace System.Collections.Tests { public class SortedList_Generic_Tests_Values : IList_Generic_Tests { + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override bool DefaultValueAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; @@ -57,6 +60,7 @@ public class SortedList_Generic_Tests_Values_AsICollection : ICollection_NonGene protected override bool NullAllowed => true; protected override bool DuplicateValuesAllowed => true; protected override bool IsReadOnly => true; + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override bool SupportsSerialization => false; diff --git a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.cs index 7fc503b3b96c97..2a7966dab14e9e 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Generic.Tests.cs @@ -13,6 +13,9 @@ namespace System.Collections.Tests public abstract class SortedList_Generic_Tests : IDictionary_Generic_Tests { #region IDictionary Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override IDictionary GenericIDictionaryFactory() { diff --git a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Tests.cs b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Tests.cs index 85bec3a725bacc..d0c7435ff5bf6a 100644 --- a/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/SortedList/SortedList.Tests.cs @@ -9,7 +9,8 @@ namespace System.Collections.Tests public class SortedList_IDictionary_NonGeneric_Tests : IDictionary_NonGeneric_Tests { #region IDictionary Helper Methods - + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override IDictionary NonGenericIDictionaryFactory() { return new SortedList(); diff --git a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs index 1edc037d7cd210..4c5c5a49d243e1 100644 --- a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs @@ -32,6 +32,9 @@ protected Stack GenericStackFactory(int count) protected override Type IGenericSharedAPI_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException); + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; + #endregion protected override IEnumerable GenericIEnumerableFactory() diff --git a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Tests.cs b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Tests.cs index 76a6e74e4f69bd..05a4cb340eae6d 100644 --- a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Tests.cs @@ -23,7 +23,9 @@ protected override ICollection NonGenericICollectionFactory() return new Stack(); } + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override Type ICollection_NonGeneric_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException); diff --git a/src/libraries/System.Private.CoreLib/src/System/ArraySegment.cs b/src/libraries/System.Private.CoreLib/src/System/ArraySegment.cs index a5431a9209295c..45fe8fdee166aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ArraySegment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ArraySegment.cs @@ -261,12 +261,18 @@ bool ICollection.Remove(T item) #region IEnumerable - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + { + ThrowInvalidOperationIfDefault(); + return + Count == 0 ? SZGenericArrayEnumerator.Empty : + new Enumerator(this); + } #endregion #region IEnumerable - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); #endregion private void ThrowInvalidOperationIfDefault() diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index 3b840d04efafd7..f959827b1da605 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -339,7 +339,8 @@ private void CopyTo(KeyValuePair[] array, int index) public Enumerator GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair); IEnumerator> IEnumerable>.GetEnumerator() => - new Enumerator(this, Enumerator.KeyValuePair); + Count == 0 ? SZGenericArrayEnumerator>.Empty : + GetEnumerator(); public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { @@ -1100,7 +1101,7 @@ void ICollection.CopyTo(Array array, int index) } } - IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); /// /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage @@ -1513,9 +1514,11 @@ bool ICollection.Remove(TKey item) return false; } - IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dictionary); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? SZGenericArrayEnumerator.Empty : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dictionary); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); void ICollection.CopyTo(Array array, int index) { @@ -1705,9 +1708,11 @@ void ICollection.Clear() => bool ICollection.Contains(TValue item) => _dictionary.ContainsValue(item); - IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dictionary); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? SZGenericArrayEnumerator.Empty : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dictionary); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); void ICollection.CopyTo(Array array, int index) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs index fe7912af6a710e..7537e2d4b80f90 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs @@ -358,9 +358,11 @@ public bool Remove(T item) public Enumerator GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? SZGenericArrayEnumerator.Empty : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); #endregion diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs index 1cfe6a699b2421..b3c165595b7654 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs @@ -625,14 +625,13 @@ public void ForEach(Action action) // while an enumeration is in progress, the MoveNext and // GetObject methods of the enumerator will throw an exception. // - public Enumerator GetEnumerator() - => new Enumerator(this); + public Enumerator GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? SZGenericArrayEnumerator.Empty : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); public List GetRange(int index, int count) { @@ -1145,6 +1144,8 @@ public bool TrueForAll(Predicate match) public struct Enumerator : IEnumerator, IEnumerator { + internal static IEnumerator? s_emptyEnumerator; + private readonly List _list; private int _index; private readonly int _version; diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs index 98a2f202556a35..ba27a5ed9d397c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs @@ -183,21 +183,14 @@ public void Enqueue(T item) // GetEnumerator returns an IEnumerator over this Queue. This // Enumerator will support removing. - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } + public Enumerator GetEnumerator() => new Enumerator(this); /// - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this); - } + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? SZGenericArrayEnumerator.Empty : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(this); - } + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); // Removes the object at the head of the queue and returns it. If the queue // is empty, this method throws an diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs index 57be3812a5142c..874db5fad5f47e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs @@ -43,9 +43,8 @@ public void CopyTo(T[] array, int index) } public IEnumerator GetEnumerator() => - list.Count == 0 ? - SZGenericArrayEnumerator.Empty : - list.GetEnumerator(); + list.Count == 0 ? SZGenericArrayEnumerator.Empty : + list.GetEnumerator(); public int IndexOf(T value) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs index 0c9806eadf4cf7..7a8f8da784c088 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs @@ -258,7 +258,7 @@ IEnumerator> IEnumerable>. { Container c = _container; return c is null || c.FirstFreeEntry == 0 ? - ((IEnumerable>)Array.Empty>()).GetEnumerator() : + SZGenericArrayEnumerator>.Empty : new Enumerator(this); } } diff --git a/src/libraries/System.Runtime/tests/System/ArraySegmentTests.cs b/src/libraries/System.Runtime/tests/System/ArraySegmentTests.cs index bbe9a8646e2fc3..171b9bf5d24eb5 100644 --- a/src/libraries/System.Runtime/tests/System/ArraySegmentTests.cs +++ b/src/libraries/System.Runtime/tests/System/ArraySegmentTests.cs @@ -24,6 +24,7 @@ protected override IList GenericIListFactory(int count) return Factory(count * 2, count / 2, count); } + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override bool IsReadOnly_ValidityValue => true;