diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs b/src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs index ddb472aaa8f1e..0b80108d95286 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs @@ -67,7 +67,7 @@ public void Reset() } } - internal class SZGenericArrayEnumeratorBase + internal abstract class SZGenericArrayEnumeratorBase { protected readonly Array _array; protected int _index; @@ -104,10 +104,17 @@ public void Dispose() internal sealed class SZGenericArrayEnumerator : SZGenericArrayEnumeratorBase, IEnumerator { - // Array.Empty is intentionally omitted here, since we don't want to pay for generic instantiations that - // wouldn't have otherwise been used. + /// Provides an empty enumerator singleton. + /// + /// If the consumer is using SZGenericArrayEnumerator elsewhere or is otherwise likely + /// to be using T[] elsewhere, this singleton should be used. Otherwise, GenericEmptyEnumerator's + /// singleton should be used instead, as it doesn't reference T[] in order to reduce footprint. + /// #pragma warning disable CA1825 - internal static readonly SZGenericArrayEnumerator Empty = new SZGenericArrayEnumerator(new T[0]); + internal static readonly SZGenericArrayEnumerator Empty = + // Array.Empty is intentionally omitted here, since we don't want to pay for generic instantiations + // that wouldn't have otherwise been used. + new SZGenericArrayEnumerator(new T[0]); #pragma warning restore CA1825 public SZGenericArrayEnumerator(T[] array) @@ -133,4 +140,46 @@ public T Current object? IEnumerator.Current => Current; } + + internal abstract class GenericEmptyEnumeratorBase + { +#pragma warning disable CA1822 // https://github.com/dotnet/roslyn-analyzers/issues/5911 + public bool MoveNext() => false; + + public object Current + { + get + { + ThrowHelper.ThrowInvalidOperationException_EnumCurrent(-1); + return default; + } + } + + public void Reset() { } + + public void Dispose() { } +#pragma warning restore CA1822 + } + + /// Provides an empty enumerator singleton. + /// + /// If the consumer is using SZGenericArrayEnumerator elsewhere or is otherwise likely + /// to be using T[] elsewhere, SZGenericArrayEnumerator's singleton should be used. Otherwise, + /// this singleton should be used, as it doesn't reference T[] in order to reduce footprint. + /// + internal sealed class GenericEmptyEnumerator : GenericEmptyEnumeratorBase, IEnumerator + { + public static readonly GenericEmptyEnumerator Instance = new(); + + private GenericEmptyEnumerator() { } + + public new T Current + { + get + { + ThrowHelper.ThrowInvalidOperationException_EnumCurrent(-1); + return default; + } + } + } } 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 f959827b1da60..339a95ed4ff70 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,7 @@ private void CopyTo(KeyValuePair[] array, int index) public Enumerator GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair); IEnumerator> IEnumerable>.GetEnumerator() => - Count == 0 ? SZGenericArrayEnumerator>.Empty : + Count == 0 ? GenericEmptyEnumerator>.Instance : GetEnumerator(); public virtual void GetObjectData(SerializationInfo info, StreamingContext context) 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 7a8f8da784c08..6e7cb22326fe3 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 ? - SZGenericArrayEnumerator>.Empty : + GenericEmptyEnumerator>.Instance : new Enumerator(this); } }