Fix which path is taken in Enumerable.Select for empty partition (#88425)
authorStephen Toub <stoub@microsoft.com>
Thu, 6 Jul 2023 01:01:51 +0000 (21:01 -0400)
committerGitHub <noreply@github.com>
Thu, 6 Jul 2023 01:01:51 +0000 (21:01 -0400)
* Fix which path is taken in Enumerable.Select for empty partition

My recent changes to improve perf of some LINQ iterators by implementing `IList<T>` caused Select on an empty partition to start preferring a path that wasn't optimized for empty.  This fixes it.  I searched and don't see any other operators that would be similarly negatively affected.

* Use is check

src/libraries/System.Linq/src/System/Linq/Enumerable.SizeOpt.cs
src/libraries/System.Linq/src/System/Linq/Enumerable.SpeedOpt.cs
src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
src/libraries/System.Linq/src/System/Linq/Select.cs

index 0c37e81..bbedf34 100644 (file)
@@ -8,5 +8,10 @@ namespace System.Linq
     public static partial class Enumerable
     {
         public static IEnumerable<TResult> Empty<TResult>() => Array.Empty<TResult>();
+
+        private static IEnumerable<TResult>? GetEmptyIfEmpty<TSource, TResult>(IEnumerable<TSource> source) =>
+            source is TSource[] { Length: 0 } ?
+                Array.Empty<TResult>() :
+                null;
     }
 }
index 56c0eac..cc74d0d 100644 (file)
@@ -8,5 +8,10 @@ namespace System.Linq
     public static partial class Enumerable
     {
         public static IEnumerable<TResult> Empty<TResult>() => EmptyPartition<TResult>.Instance;
+
+        private static IEnumerable<TResult>? GetEmptyIfEmpty<TSource, TResult>(IEnumerable<TSource> source) =>
+            source is EmptyPartition<TSource> ?
+                EmptyPartition<TResult>.Instance :
+                null;
     }
 }
index bfa8d83..7eed584 100644 (file)
@@ -13,9 +13,7 @@ namespace System.Linq
         static partial void CreateSelectIPartitionIterator<TResult, TSource>(
             Func<TSource, TResult> selector, IPartition<TSource> partition, ref IEnumerable<TResult>? result)
         {
-            result = partition is EmptyPartition<TSource> ?
-                EmptyPartition<TResult>.Instance :
-                new SelectIPartitionIterator<TSource, TResult>(partition, selector);
+            result = new SelectIPartitionIterator<TSource, TResult>(partition, selector);
         }
 
         private sealed partial class SelectEnumerableIterator<TSource, TResult> : IIListProvider<TResult>
index 9d3dcc4..403016e 100644 (file)
@@ -28,6 +28,11 @@ namespace System.Linq
                 return iterator.Select(selector);
             }
 
+            if (GetEmptyIfEmpty<TSource, TResult>(source) is IEnumerable<TResult> empty)
+            {
+                return empty;
+            }
+
             if (source is IList<TSource> ilist)
             {
                 if (source is TSource[] array)