diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs
index 62bac18eba58e7..aa7a08ee81c9f6 100644
--- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs
+++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
namespace System.Linq
{
@@ -23,7 +24,7 @@ public static partial class Enumerable
/// This method compares elements by using the default comparer .
///
public static IOrderedEnumerable Order(this IEnumerable source) =>
- OrderBy(source, EnumerableSorter.IdentityFunc);
+ Order(source, comparer: null);
///
/// Sorts the elements of a sequence in ascending order.
@@ -42,7 +43,9 @@ public static IOrderedEnumerable Order(this IEnumerable source) =>
/// If comparer is , the default comparer is used to compare elements.
///
public static IOrderedEnumerable Order(this IEnumerable source, IComparer? comparer) =>
- OrderBy(source, EnumerableSorter.IdentityFunc, comparer);
+ TypeIsImplicitlyStable() && (comparer is null || comparer == Comparer.Default) ?
+ new OrderedImplicitlyStableEnumerable(source, descending: false) :
+ OrderBy(source, EnumerableSorter.IdentityFunc, comparer);
public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector)
=> new OrderedEnumerable(source, keySelector, null, false, null);
@@ -66,7 +69,7 @@ public static IOrderedEnumerable OrderBy(this IEnumerabl
/// This method compares elements by using the default comparer .
///
public static IOrderedEnumerable OrderDescending(this IEnumerable source) =>
- OrderByDescending(source, EnumerableSorter.IdentityFunc);
+ OrderDescending(source, comparer: null);
///
/// Sorts the elements of a sequence in descending order.
@@ -85,7 +88,9 @@ public static IOrderedEnumerable OrderDescending(this IEnumerable sourc
/// If comparer is , the default comparer is used to compare elements.
///
public static IOrderedEnumerable OrderDescending(this IEnumerable source, IComparer? comparer) =>
- OrderByDescending(source, EnumerableSorter.IdentityFunc, comparer);
+ TypeIsImplicitlyStable() && (comparer is null || comparer == Comparer.Default) ?
+ new OrderedImplicitlyStableEnumerable(source, descending: true) :
+ OrderByDescending(source, EnumerableSorter.IdentityFunc, comparer);
public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) =>
new OrderedEnumerable(source, keySelector, null, true, null);
@@ -132,6 +137,17 @@ public static IOrderedEnumerable ThenByDescending(this I
return source.CreateOrderedEnumerable(keySelector, comparer, true);
}
+
+ /// Gets whether the results of an unstable sort will be observably the same as a stable sort.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool TypeIsImplicitlyStable() =>
+ typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte) ||
+ typeof(T) == typeof(int) || typeof(T) == typeof(uint) ||
+ typeof(T) == typeof(short) || typeof(T) == typeof(ushort) ||
+ typeof(T) == typeof(long) || typeof(T) == typeof(ulong) ||
+ typeof(T) == typeof(Int128) || typeof(T) == typeof(UInt128) ||
+ typeof(T) == typeof(nint) || typeof(T) == typeof(nuint) ||
+ typeof(T) == typeof(bool) || typeof(T) == typeof(char);
}
public interface IOrderedEnumerable : IEnumerable
diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
index b90dbf28a9537b..6ea4b069a1e9ae 100644
--- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
@@ -3,13 +3,13 @@
using System.Collections;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
namespace System.Linq
{
internal abstract partial class OrderedEnumerable : IPartition
{
- public TElement[] ToArray()
+ public virtual TElement[] ToArray()
{
Buffer buffer = new Buffer(_source);
@@ -29,7 +29,7 @@ public TElement[] ToArray()
return array;
}
- public List ToList()
+ public virtual List ToList()
{
Buffer buffer = new Buffer(_source);
int count = buffer._count;
@@ -247,4 +247,21 @@ private TElement Last(Buffer buffer)
return value;
}
}
+
+ internal sealed partial class OrderedImplicitlyStableEnumerable : OrderedEnumerable
+ {
+ public override TElement[] ToArray()
+ {
+ TElement[] array = _source.ToArray();
+ Sort(array, _descending);
+ return array;
+ }
+
+ public override List ToList()
+ {
+ List list = _source.ToList();
+ Sort(CollectionsMarshal.AsSpan(list), _descending);
+ return list;
+ }
+ }
}
diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
index 36f6b992712c33..1bcdb6b2b780d1 100644
--- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
+++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
@@ -18,7 +18,7 @@ internal abstract partial class OrderedEnumerable : IOrderedEnumerable
private int[] SortedMap(Buffer buffer, int minIdx, int maxIdx) =>
GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx);
- public IEnumerator GetEnumerator()
+ public virtual IEnumerator GetEnumerator()
{
Buffer buffer = new Buffer(_source);
if (buffer._count > 0)
@@ -62,9 +62,7 @@ internal IEnumerator GetEnumerator(int minIdx, int maxIdx)
internal abstract EnumerableSorter GetEnumerableSorter(EnumerableSorter? next);
- private CachingComparer GetComparer() => GetComparer(null);
-
- internal abstract CachingComparer GetComparer(CachingComparer? childComparer);
+ internal abstract CachingComparer GetComparer(CachingComparer? childComparer = null);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
@@ -159,6 +157,57 @@ internal override CachingComparer GetComparer(CachingComparerAn ordered enumerable used by Order/OrderDescending for Ts that are bitwise indistinguishable for any considered equal.
+ internal sealed partial class OrderedImplicitlyStableEnumerable : OrderedEnumerable
+ {
+ private readonly bool _descending;
+
+ public OrderedImplicitlyStableEnumerable(IEnumerable source, bool descending) : base(source)
+ {
+ Debug.Assert(Enumerable.TypeIsImplicitlyStable());
+
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
+ _descending = descending;
+ }
+
+ internal override CachingComparer GetComparer(CachingComparer? childComparer) =>
+ childComparer == null ?
+ new CachingComparer(EnumerableSorter.IdentityFunc, Comparer.Default, _descending) :
+ new CachingComparerWithChild(EnumerableSorter.IdentityFunc, Comparer.Default, _descending, childComparer);
+
+ internal override EnumerableSorter GetEnumerableSorter(EnumerableSorter? next) =>
+ new EnumerableSorter(EnumerableSorter.IdentityFunc, Comparer.Default, _descending, next);
+
+ public override IEnumerator GetEnumerator()
+ {
+ var buffer = new Buffer(_source);
+ if (buffer._count > 0)
+ {
+ Sort(buffer._items.AsSpan(0, buffer._count), _descending);
+ for (int i = 0; i < buffer._count; i++)
+ {
+ yield return buffer._items[i];
+ }
+ }
+ }
+
+ private static void Sort(Span span, bool descending)
+ {
+ if (descending)
+ {
+ span.Sort(static (a, b) => Comparer.Default.Compare(b, a));
+ }
+ else
+ {
+ span.Sort();
+ }
+ }
+ }
+
// A comparer that chains comparisons, and pushes through the last element found to be
// lower or higher (depending on use), so as to represent the sort of comparisons
// done by OrderBy().ThenBy() combinations.
diff --git a/src/libraries/System.Linq/tests/OrderDescendingTests.cs b/src/libraries/System.Linq/tests/OrderDescendingTests.cs
index 2e77f6a9da189a..47fd110aab1363 100644
--- a/src/libraries/System.Linq/tests/OrderDescendingTests.cs
+++ b/src/libraries/System.Linq/tests/OrderDescendingTests.cs
@@ -103,6 +103,48 @@ public void SourceReverseOfResultNullPassedAsComparer()
Assert.Equal(expected, source.OrderDescending(null));
}
+ [Fact]
+ public void OrderedDescendingToArray()
+ {
+ var source = new[]
+ {
+ 5, 9, 6, 7, 8, 5, 20
+ };
+ var expected = new[]
+ {
+ 20, 9, 8, 7, 6, 5, 5
+ };
+
+ Assert.Equal(expected, source.OrderDescending().ToArray());
+ }
+
+ [Fact]
+ public void EmptyOrderedDescendingToArray()
+ {
+ Assert.Empty(Enumerable.Empty().OrderDescending().ToArray());
+ }
+
+ [Fact]
+ public void OrderedDescendingToList()
+ {
+ var source = new[]
+ {
+ 5, 9, 6, 7, 8, 5, 20
+ };
+ var expected = new[]
+ {
+ 20, 9, 8, 7, 6, 5, 5
+ };
+
+ Assert.Equal(expected, source.OrderDescending().ToList());
+ }
+
+ [Fact]
+ public void EmptyOrderedDescendingToList()
+ {
+ Assert.Empty(Enumerable.Empty().OrderDescending().ToList());
+ }
+
[Fact]
public void SameKeysVerifySortStable()
{
diff --git a/src/libraries/System.Linq/tests/OrderTests.cs b/src/libraries/System.Linq/tests/OrderTests.cs
index 6258f06c8b67e8..3560959b6a8ea4 100644
--- a/src/libraries/System.Linq/tests/OrderTests.cs
+++ b/src/libraries/System.Linq/tests/OrderTests.cs
@@ -485,5 +485,13 @@ public void CultureOrderElementAt()
}
}
}
+
+ [Fact]
+ public void StableSort_CustomComparerAlwaysReturns0()
+ {
+ byte[] values = new byte[] { 0x45, 0x7D, 0x4B, 0x61, 0x27 };
+ byte[] newValues = values.Order(Comparer.Create((a, b) => 0)).ToArray();
+ AssertExtensions.SequenceEqual(values, newValues);
+ }
}
}