Add dedicated empty array enumerator for footprint reduction (#82899)
authorStephen Toub <stoub@microsoft.com>
Thu, 2 Mar 2023 21:19:51 +0000 (16:19 -0500)
committerGitHub <noreply@github.com>
Thu, 2 Mar 2023 21:19:51 +0000 (16:19 -0500)
* Add dedicated empty array enumerator for footprint reduction

Its singleton can be used in places where it's less likely the consumer will otherwise be using a T[], such that it's less likely we'll need to generate code for T[].

* Address PR feedback

src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs

index ddb472a..0b80108 100644 (file)
@@ -67,7 +67,7 @@ namespace System
         }
     }
 
-    internal class SZGenericArrayEnumeratorBase
+    internal abstract class SZGenericArrayEnumeratorBase
     {
         protected readonly Array _array;
         protected int _index;
@@ -104,10 +104,17 @@ namespace System
 
     internal sealed class SZGenericArrayEnumerator<T> : SZGenericArrayEnumeratorBase, IEnumerator<T>
     {
-        // Array.Empty is intentionally omitted here, since we don't want to pay for generic instantiations that
-        // wouldn't have otherwise been used.
+        /// <summary>Provides an empty enumerator singleton.</summary>
+        /// <remarks>
+        /// 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.
+        /// </remarks>
 #pragma warning disable CA1825
-        internal static readonly SZGenericArrayEnumerator<T> Empty = new SZGenericArrayEnumerator<T>(new T[0]);
+        internal static readonly SZGenericArrayEnumerator<T> 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<T>(new T[0]);
 #pragma warning restore CA1825
 
         public SZGenericArrayEnumerator(T[] array)
@@ -133,4 +140,46 @@ namespace System
 
         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
+    }
+
+    /// <summary>Provides an empty enumerator singleton.</summary>
+    /// <remarks>
+    /// 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.
+    /// </remarks>
+    internal sealed class GenericEmptyEnumerator<T> : GenericEmptyEnumeratorBase, IEnumerator<T>
+    {
+        public static readonly GenericEmptyEnumerator<T> Instance = new();
+
+        private GenericEmptyEnumerator() { }
+
+        public new T Current
+        {
+            get
+            {
+                ThrowHelper.ThrowInvalidOperationException_EnumCurrent(-1);
+                return default;
+            }
+        }
+    }
 }
index f959827..339a95e 100644 (file)
@@ -339,7 +339,7 @@ namespace System.Collections.Generic
         public Enumerator GetEnumerator() => new Enumerator(this, Enumerator.KeyValuePair);
 
         IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() =>
-            Count == 0 ? SZGenericArrayEnumerator<KeyValuePair<TKey, TValue>>.Empty :
+            Count == 0 ? GenericEmptyEnumerator<KeyValuePair<TKey, TValue>>.Instance :
             GetEnumerator();
 
         public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
index 7a8f8da..6e7cb22 100644 (file)
@@ -258,7 +258,7 @@ namespace System.Runtime.CompilerServices
             {
                 Container c = _container;
                 return c is null || c.FirstFreeEntry == 0 ?
-                    SZGenericArrayEnumerator<KeyValuePair<TKey, TValue>>.Empty :
+                    GenericEmptyEnumerator<KeyValuePair<TKey, TValue>>.Instance :
                     new Enumerator(this);
             }
         }