From de029dd4d0adcfbededee21e59c7c7cf6fa8564b Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Thu, 9 Jun 2022 23:52:37 +0200 Subject: [PATCH 01/17] Implement proposal for `System.Linq` --- src/libraries/System.Linq/ref/System.Linq.cs | 4 + .../System.Linq/src/System/Linq/OrderBy.cs | 12 ++ .../src/System/Linq/OrderedEnumerable.cs | 113 ++++++++++++++++++ .../System.Linq/tests/OrderDescendingTests.cs | 42 +++++++ src/libraries/System.Linq/tests/OrderTests.cs | 42 +++++++ .../tests/System.Linq.Tests.csproj | 2 + 6 files changed, 215 insertions(+) create mode 100644 src/libraries/System.Linq/tests/OrderDescendingTests.cs create mode 100644 src/libraries/System.Linq/tests/OrderTests.cs diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index 5b2d9996512ae..215ec4b4d34e8 100644 --- a/src/libraries/System.Linq/ref/System.Linq.cs +++ b/src/libraries/System.Linq/ref/System.Linq.cs @@ -147,6 +147,10 @@ public static System.Collections.Generic.IEnumerable< public static System.Linq.IOrderedEnumerable OrderByDescending(this System.Collections.Generic.IEnumerable source, System.Func keySelector, System.Collections.Generic.IComparer? comparer) { throw null; } public static System.Linq.IOrderedEnumerable OrderBy(this System.Collections.Generic.IEnumerable source, System.Func keySelector) { throw null; } public static System.Linq.IOrderedEnumerable OrderBy(this System.Collections.Generic.IEnumerable source, System.Func keySelector, System.Collections.Generic.IComparer? comparer) { throw null; } + public static System.Linq.IOrderedEnumerable OrderDescending(this System.Collections.Generic.IEnumerable source) { throw null; } + public static System.Linq.IOrderedEnumerable OrderDescending(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IComparer comparer) { throw null; } + public static System.Linq.IOrderedEnumerable Order(this System.Collections.Generic.IEnumerable source) { throw null; } + public static System.Linq.IOrderedEnumerable Order(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IComparer comparer) { throw null; } public static System.Collections.Generic.IEnumerable Prepend(this System.Collections.Generic.IEnumerable source, TSource element) { throw null; } public static System.Collections.Generic.IEnumerable Range(int start, int count) { throw null; } public static System.Collections.Generic.IEnumerable Repeat(TResult element, int count) { throw null; } diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index e3e3dc313d537..dda104cd98be8 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -7,12 +7,24 @@ namespace System.Linq { public static partial class Enumerable { + public static IOrderedEnumerable Order(this IEnumerable source) => + new OrderedKeylessEnumerable(source, null, false, null); + + public static IOrderedEnumerable Order(this IEnumerable source, IComparer comparer) => + new OrderedKeylessEnumerable(source, comparer, false, null); + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) => new OrderedEnumerable(source, keySelector, null, false, null); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) => new OrderedEnumerable(source, keySelector, comparer, false, null); + public static IOrderedEnumerable OrderDescending(this IEnumerable source) => + new OrderedKeylessEnumerable(source, null, true, null); + + public static IOrderedEnumerable OrderDescending(this IEnumerable source, IComparer comparer) => + new OrderedKeylessEnumerable(source, comparer, true, null); + public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) => new OrderedEnumerable(source, keySelector, null, true, null); diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs index 2c8cf01f941d6..5095eaeebef41 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -106,6 +106,53 @@ IOrderedEnumerable IOrderedEnumerable.CreateOrderedEnumerabl } } + internal sealed class OrderedKeylessEnumerable : OrderedEnumerable + { + private readonly OrderedEnumerable? _parent; + private readonly IComparer _comparer; + private readonly bool _descending; + + internal OrderedKeylessEnumerable(IEnumerable source, IComparer? comparer, bool descending, OrderedEnumerable? parent) : base(source) + { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _parent = parent; + _comparer = comparer ?? Comparer.Default; + _descending = descending; + } + + internal override EnumerableSorter GetEnumerableSorter(EnumerableSorter? next) + { + // Special case the common use of string with default comparer. Comparer.Default checks the + // thread's Culture on each call which is an overhead which is not required, because we are about to + // do a sort which remains on the current thread (and EnumerableSorter is not used afterwards). + IComparer comparer = _comparer; + if (typeof(TElement) == typeof(string) && comparer == Comparer.Default) + { + comparer = (IComparer)StringComparer.CurrentCulture; + } + + EnumerableSorter sorter = new EnumerableSorter(static item => item, comparer, _descending, next); + if (_parent != null) + { + sorter = _parent.GetEnumerableSorter(sorter); + } + + return sorter; + } + + internal override CachingComparer GetComparer(CachingComparer? childComparer) + { + CachingComparer cmp = childComparer == null + ? new KeylessCachingComparer(_comparer, _descending) + : new KeylessCachingComparerWithChild(_comparer, _descending, childComparer); + return _parent != null ? _parent.GetComparer(cmp) : cmp; + } + } + internal sealed class OrderedEnumerable : OrderedEnumerable { private readonly OrderedEnumerable? _parent; @@ -202,6 +249,35 @@ internal override void SetElement(TElement element) } } + internal sealed class KeylessCachingComparer : CachingComparer + { + private readonly IComparer _comparer; + private readonly bool _descending; + private TElement? _lastElement; + + public KeylessCachingComparer(IComparer comparer, bool descending) + { + _comparer = comparer; + _descending = descending; + } + + internal override int Compare(TElement element, bool cacheLower) + { + int cmp = _descending ? _comparer.Compare(_lastElement, element) : _comparer.Compare(element, _lastElement); + if (cacheLower == cmp < 0) + { + _lastElement = element; + } + + return cmp; + } + + internal override void SetElement(TElement element) + { + _lastElement = element; + } + } + internal sealed class CachingComparerWithChild : CachingComparer { private readonly CachingComparer _child; @@ -237,6 +313,43 @@ internal override void SetElement(TElement element) } } + internal sealed class KeylessCachingComparerWithChild : CachingComparer + { + private readonly IComparer _comparer; + private readonly bool _descending; + private readonly CachingComparer _child; + private TElement? _lastElement; + + public KeylessCachingComparerWithChild(IComparer comparer, bool descending, CachingComparer child) + { + _comparer = comparer; + _descending = descending; + _child = child; + } + + internal override int Compare(TElement element, bool cacheLower) + { + int cmp = _descending ? _comparer.Compare(_lastElement, element) : _comparer.Compare(element, _lastElement); + if (cmp == 0) + { + return _child.Compare(element, cacheLower); + } + + if (cacheLower == cmp < 0) + { + _lastElement = element; + _child.SetElement(element); + } + + return cmp; + } + + internal override void SetElement(TElement element) + { + _lastElement = element; + } + } + internal abstract class EnumerableSorter { internal abstract void ComputeKeys(TElement[] elements, int count); diff --git a/src/libraries/System.Linq/tests/OrderDescendingTests.cs b/src/libraries/System.Linq/tests/OrderDescendingTests.cs new file mode 100644 index 0000000000000..e9bc6f84b5db4 --- /dev/null +++ b/src/libraries/System.Linq/tests/OrderDescendingTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Linq.Tests +{ + public sealed class OrderDescendingTests : EnumerableTests + { + [Fact] + public void Order() + { + int[] source = { 9, 1, 3, 2, 5, 4, 6, 7, 8, 0 }; + int[] expected = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + + Assert.Equal(expected, source.OrderDescending().ToArray()); + } + + [Fact] + public void SourceEmpty() + { + int[] source = { }; + Assert.Empty(source.OrderDescending()); + } + + [Fact] + public void OrderedCount() + { + var source = Enumerable.Range(0, 20).Shuffle(); + Assert.Equal(20, source.OrderDescending().Count()); + } + + [Fact] + public void ElementsAllSameKey() + { + int?[] source = { 9, 9, 9, 9, 9, 9 }; + int?[] expected = { 9, 9, 9, 9, 9, 9 }; + + Assert.Equal(expected, source.OrderDescending()); + } + } +} diff --git a/src/libraries/System.Linq/tests/OrderTests.cs b/src/libraries/System.Linq/tests/OrderTests.cs new file mode 100644 index 0000000000000..3027853440629 --- /dev/null +++ b/src/libraries/System.Linq/tests/OrderTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Linq.Tests +{ + public sealed class OrderTests : EnumerableTests + { + [Fact] + public void Order() + { + int[] source = { 9, 1, 3, 2, 5, 4, 6, 7, 8, 0 }; + int[] expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + Assert.Equal(expected, source.Order().ToArray()); + } + + [Fact] + public void SourceEmpty() + { + int[] source = { }; + Assert.Empty(source.Order()); + } + + [Fact] + public void OrderedCount() + { + var source = Enumerable.Range(0, 20).Shuffle(); + Assert.Equal(20, source.Order().Count()); + } + + [Fact] + public void ElementsAllSameKey() + { + int?[] source = { 9, 9, 9, 9, 9, 9 }; + int?[] expected = { 9, 9, 9, 9, 9, 9 }; + + Assert.Equal(expected, source.Order()); + } + } +} diff --git a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj index 5cbd9676c14f9..4f7231e72b4de 100644 --- a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj @@ -39,7 +39,9 @@ + + From 23d2081b973f57b4e720a1008616549856f79493 Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Fri, 10 Jun 2022 00:10:07 +0200 Subject: [PATCH 02/17] Implement proposal for `System.Linq.Queryable` --- .../ref/System.Linq.Queryable.cs | 4 ++ .../src/System/Linq/CachedReflection.cs | 24 +++++++++ .../src/System/Linq/Queryable.cs | 52 +++++++++++++++++++ .../tests/OrderDescendingTests.cs | 26 ++++++++++ .../System.Linq.Queryable/tests/OrderTests.cs | 26 ++++++++++ .../tests/System.Linq.Queryable.Tests.csproj | 2 + .../tests/TrimCompatibilityTests.cs | 2 +- 7 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs create mode 100644 src/libraries/System.Linq.Queryable/tests/OrderTests.cs diff --git a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs index 31807f5069001..f8282977280bf 100644 --- a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs +++ b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs @@ -134,6 +134,10 @@ public static partial class Queryable public static System.Linq.IOrderedQueryable OrderByDescending(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> keySelector, System.Collections.Generic.IComparer? comparer) { throw null; } public static System.Linq.IOrderedQueryable OrderBy(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> keySelector) { throw null; } public static System.Linq.IOrderedQueryable OrderBy(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> keySelector, System.Collections.Generic.IComparer? comparer) { throw null; } + public static System.Linq.IOrderedQueryable OrderDescending(this System.Linq.IQueryable source) { throw null; } + public static System.Linq.IOrderedQueryable OrderDescending(this System.Linq.IQueryable source, System.Collections.Generic.IComparer comparer) { throw null; } + public static System.Linq.IOrderedQueryable Order(this System.Linq.IQueryable source) { throw null; } + public static System.Linq.IOrderedQueryable Order(this System.Linq.IQueryable source, System.Collections.Generic.IComparer comparer) { throw null; } public static System.Linq.IQueryable Prepend(this System.Linq.IQueryable source, TSource element) { throw null; } public static System.Linq.IQueryable Reverse(this System.Linq.IQueryable source) { throw null; } public static System.Linq.IQueryable SelectMany(this System.Linq.IQueryable source, System.Linq.Expressions.Expression>> selector) { throw null; } diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs index f51ac29927f48..99c98faf7ecbe 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs @@ -530,12 +530,36 @@ public static MethodInfo OfType_TResult_1(Type TResult) => (s_OfType_TResult_1 ??= new Func>(Queryable.OfType).GetMethodInfo().GetGenericMethodDefinition()) .MakeGenericMethod(TResult); + private static MethodInfo? s_Order_T_1; + + public static MethodInfo Order_T_1(Type T) => + (s_Order_T_1 ??= new Func, IOrderedQueryable>(Queryable.Order).GetMethodInfo().GetGenericMethodDefinition()) + .MakeGenericMethod(T); + + private static MethodInfo? s_Order_T_2; + + public static MethodInfo Order_T_2(Type T) => + (s_Order_T_2 ??= new Func, IComparer, IOrderedQueryable>(Queryable.Order).GetMethodInfo().GetGenericMethodDefinition()) + .MakeGenericMethod(T); + private static MethodInfo? s_OrderBy_TSource_TKey_2; public static MethodInfo OrderBy_TSource_TKey_2(Type TSource, Type TKey) => (s_OrderBy_TSource_TKey_2 ??= new Func, Expression>, IOrderedQueryable>(Queryable.OrderBy).GetMethodInfo().GetGenericMethodDefinition()) .MakeGenericMethod(TSource, TKey); + private static MethodInfo? s_OrderDescending_T_1; + + public static MethodInfo OrderDescending_T_1(Type T) => + (s_OrderDescending_T_1 ??= new Func, IOrderedQueryable>(Queryable.OrderDescending).GetMethodInfo().GetGenericMethodDefinition()) + .MakeGenericMethod(T); + + private static MethodInfo? s_OrderDescending_T_2; + + public static MethodInfo OrderDescending_T_2(Type T) => + (s_OrderDescending_T_2 ??= new Func, IComparer, IOrderedQueryable>(Queryable.OrderDescending).GetMethodInfo().GetGenericMethodDefinition()) + .MakeGenericMethod(T); + private static MethodInfo? s_OrderBy_TSource_TKey_3; public static MethodInfo OrderBy_TSource_TKey_3(Type TSource, Type TKey) => diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs index ad79e2fe95096..9a86789f76cb6 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs @@ -241,6 +241,32 @@ public static IQueryable GroupJoin(this CachedReflectionInfo.GroupJoin_TOuter_TInner_TKey_TResult_6(typeof(TOuter), typeof(TInner), typeof(TKey), typeof(TResult)), outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector), Expression.Constant(comparer, typeof(IEqualityComparer)))); } + [DynamicDependency("Order`1", typeof(Enumerable))] + public static IOrderedQueryable Order(this IQueryable source) + { + ArgumentNullException.ThrowIfNull(source); + + return (IOrderedQueryable)source.Provider.CreateQuery( + Expression.Call( + null, + CachedReflectionInfo.Order_T_1(typeof(T)), + source.Expression + )); + } + + [DynamicDependency("Order`1", typeof(Enumerable))] + public static IOrderedQueryable Order(this IQueryable source, IComparer comparer) + { + ArgumentNullException.ThrowIfNull(source); + + return (IOrderedQueryable)source.Provider.CreateQuery( + Expression.Call( + null, + CachedReflectionInfo.Order_T_2(typeof(T)), + source.Expression, Expression.Constant(comparer, typeof(IComparer)) + )); + } + [DynamicDependency("OrderBy`2", typeof(Enumerable))] public static IOrderedQueryable OrderBy(this IQueryable source, Expression> keySelector) { @@ -269,6 +295,32 @@ public static IOrderedQueryable OrderBy(this IQueryable< )); } + [DynamicDependency("OrderDescending`1", typeof(Enumerable))] + public static IOrderedQueryable OrderDescending(this IQueryable source) + { + ArgumentNullException.ThrowIfNull(source); + + return (IOrderedQueryable)source.Provider.CreateQuery( + Expression.Call( + null, + CachedReflectionInfo.OrderDescending_T_1(typeof(T)), + source.Expression + )); + } + + [DynamicDependency("OrderDescending`1", typeof(Enumerable))] + public static IOrderedQueryable OrderDescending(this IQueryable source, IComparer comparer) + { + ArgumentNullException.ThrowIfNull(source); + + return (IOrderedQueryable)source.Provider.CreateQuery( + Expression.Call( + null, + CachedReflectionInfo.OrderDescending_T_2(typeof(T)), + source.Expression, Expression.Constant(comparer, typeof(IComparer)) + )); + } + [DynamicDependency("OrderByDescending`2", typeof(Enumerable))] public static IOrderedQueryable OrderByDescending(this IQueryable source, Expression> keySelector) { diff --git a/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs b/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs new file mode 100644 index 0000000000000..9a5bc5524a629 --- /dev/null +++ b/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Linq.Tests +{ + public sealed class OrderDescendingTests : EnumerableBasedTests + { + [Fact] + public void OrderDescending1() + { + var count = (new int[] { 0, 1, 2 }).AsQueryable().OrderDescending().Count(); + Assert.Equal(3, count); + } + + [Fact] + public void OrderDescending2() + { + var count = (new int[] { 0, 1, 2 }).AsQueryable().OrderDescending(Comparer.Default).Count(); + Assert.Equal(3, count); + } + } +} diff --git a/src/libraries/System.Linq.Queryable/tests/OrderTests.cs b/src/libraries/System.Linq.Queryable/tests/OrderTests.cs new file mode 100644 index 0000000000000..a2efe2ed0c5ad --- /dev/null +++ b/src/libraries/System.Linq.Queryable/tests/OrderTests.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Linq.Tests +{ + public sealed class OrderTests : EnumerableBasedTests + { + [Fact] + public void Order1() + { + var count = (new int[] { 0, 1, 2 }).AsQueryable().Order().Count(); + Assert.Equal(3, count); + } + + [Fact] + public void Order2() + { + var count = (new int[] { 0, 1, 2 }).AsQueryable().Order(Comparer.Default).Count(); + Assert.Equal(3, count); + } + } +} diff --git a/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj b/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj index 0d72e46d8067c..4a5a3ef305bab 100644 --- a/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj +++ b/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj @@ -34,6 +34,8 @@ + + diff --git a/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs b/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs index 9021f6abd1be7..dfcff830d8dee 100644 --- a/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs @@ -62,7 +62,7 @@ public static void CachedReflectionInfoMethodsNoAnnotations() .Where(m => m.GetParameters().Length > 0); // If you are adding a new method to this class, ensure the method meets these requirements - Assert.Equal(131, methods.Count()); + Assert.Equal(135, methods.Count()); foreach (MethodInfo method in methods) { ParameterInfo[] parameters = method.GetParameters(); From ea53a6c512309e8409ad70290f30a401ca3947e3 Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Fri, 10 Jun 2022 00:37:08 +0200 Subject: [PATCH 03/17] Add documentation --- .../src/System/Linq/Queryable.cs | 98 +++++++++++++++++ .../System.Linq/src/System/Linq/OrderBy.cs | 102 ++++++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs index 9a86789f76cb6..48551411c8998 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs @@ -241,6 +241,30 @@ public static IQueryable GroupJoin(this CachedReflectionInfo.GroupJoin_TOuter_TInner_TKey_TResult_6(typeof(TOuter), typeof(TInner), typeof(TKey), typeof(TResult)), outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector), Expression.Constant(comparer, typeof(IEqualityComparer)))); } + /// + /// Sorts the elements of a sequence in ascending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An whose elements are sorted. + /// is . + /// + /// This method has at least one parameter of type whose type argument is one + /// of the types. + /// For these parameters, you can pass in a lambda expression and it will be compiled to an . + /// + /// The method generates a that represents + /// calling itself as a constructed generic method. + /// It then passes the to the method + /// of the represented by the property of the + /// parameter. The result of calling is cast to + /// type and returned. + /// + /// The query behavior that occurs as a result of executing an expression tree + /// that represents calling + /// depends on the implementation of the parameter. + /// The expected behavior is that it sorts the elements of by itself. + /// [DynamicDependency("Order`1", typeof(Enumerable))] public static IOrderedQueryable Order(this IQueryable source) { @@ -254,6 +278,31 @@ public static IOrderedQueryable Order(this IQueryable source) )); } + /// + /// Sorts the elements of a sequence in ascending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An to compare elements. + /// An whose elements are sorted. + /// is . + /// + /// This method has at least one parameter of type whose type argument is one + /// of the types. + /// For these parameters, you can pass in a lambda expression and it will be compiled to an . + /// + /// The method generates a that represents + /// calling itself as a constructed generic method. + /// It then passes the to the method + /// of the represented by the property of the + /// parameter. The result of calling is cast to + /// type and returned. + /// + /// The query behavior that occurs as a result of executing an expression tree + /// that represents calling + /// depends on the implementation of the parameter. + /// The expected behavior is that it sorts the elements of by itself. + /// [DynamicDependency("Order`1", typeof(Enumerable))] public static IOrderedQueryable Order(this IQueryable source, IComparer comparer) { @@ -295,6 +344,30 @@ public static IOrderedQueryable OrderBy(this IQueryable< )); } + /// + /// Sorts the elements of a sequence in descending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An whose elements are sorted. + /// is . + /// + /// This method has at least one parameter of type whose type argument is one + /// of the types. + /// For these parameters, you can pass in a lambda expression and it will be compiled to an . + /// + /// The method generates a that represents + /// calling itself as a constructed generic method. + /// It then passes the to the method + /// of the represented by the property of the + /// parameter. The result of calling is cast to + /// type and returned. + /// + /// The query behavior that occurs as a result of executing an expression tree + /// that represents calling + /// depends on the implementation of the parameter. + /// The expected behavior is that it sorts the elements of by itself. + /// [DynamicDependency("OrderDescending`1", typeof(Enumerable))] public static IOrderedQueryable OrderDescending(this IQueryable source) { @@ -308,6 +381,31 @@ public static IOrderedQueryable OrderDescending(this IQueryable source) )); } + /// + /// Sorts the elements of a sequence in descending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An to compare elements. + /// An whose elements are sorted. + /// is . + /// + /// This method has at least one parameter of type whose type argument is one + /// of the types. + /// For these parameters, you can pass in a lambda expression and it will be compiled to an . + /// + /// The method generates a that represents + /// calling itself as a constructed generic method. + /// It then passes the to the method + /// of the represented by the property of the + /// parameter. The result of calling is cast to + /// type and returned. + /// + /// The query behavior that occurs as a result of executing an expression tree + /// that represents calling + /// depends on the implementation of the parameter. + /// The expected behavior is that it sorts the elements of by itself. + /// [DynamicDependency("OrderDescending`1", typeof(Enumerable))] public static IOrderedQueryable OrderDescending(this IQueryable source, IComparer comparer) { diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index dda104cd98be8..0cf341912e02b 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -7,9 +7,60 @@ namespace System.Linq { public static partial class Enumerable { + /// + /// Sorts the elements of a sequence in ascending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An whose elements are sorted. + /// is . + /// + /// This method is implemented by using deferred execution. The immediate return value is an object + /// that stores all the information that is required to perform the action. + /// The query represented by this method is not executed until the object is enumerated by calling + /// its method. + /// + /// Two methods are defined to extend the type , which is the return type of this method. + /// These two methods, namely + /// and , enable you to specify additional + /// sort criteria to sort a sequence. + /// + /// and also return + /// an , which means any number of consecutive calls + /// to + /// or can be made. + /// + /// This method compares keys by using the default comparer . + /// public static IOrderedEnumerable Order(this IEnumerable source) => new OrderedKeylessEnumerable(source, null, false, null); + /// + /// Sorts the elements of a sequence in ascending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An to compare keys. + /// An whose elements are sorted. + /// is . + /// + /// This method is implemented by using deferred execution. The immediate return value is an object + /// that stores all the information that is required to perform the action. + /// The query represented by this method is not executed until the object is enumerated by calling + /// its method. + /// + /// Two methods are defined to extend the type , which is the return type of this method. + /// These two methods, namely + /// and , enable you to specify additional + /// sort criteria to sort a sequence. + /// + /// and also return + /// an , which means any number of consecutive calls + /// to + /// or can be made. + /// + /// If comparer is , the default comparer is used to compare keys. + /// public static IOrderedEnumerable Order(this IEnumerable source, IComparer comparer) => new OrderedKeylessEnumerable(source, comparer, false, null); @@ -19,9 +70,60 @@ public static IOrderedEnumerable OrderBy(this IEnumerabl public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) => new OrderedEnumerable(source, keySelector, comparer, false, null); + /// + /// Sorts the elements of a sequence in descending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An whose elements are sorted. + /// is . + /// + /// This method is implemented by using deferred execution. The immediate return value is an object + /// that stores all the information that is required to perform the action. + /// The query represented by this method is not executed until the object is enumerated by calling + /// its method. + /// + /// Two methods are defined to extend the type , which is the return type of this method. + /// These two methods, namely + /// and , enable you to specify additional + /// sort criteria to sort a sequence. + /// + /// and also return + /// an , which means any number of consecutive calls + /// to + /// or can be made. + /// + /// This method compares keys by using the default comparer . + /// public static IOrderedEnumerable OrderDescending(this IEnumerable source) => new OrderedKeylessEnumerable(source, null, true, null); + /// + /// Sorts the elements of a sequence in descending order. + /// + /// The type of the elements of . + /// A sequence of values to order. + /// An to compare keys. + /// An whose elements are sorted. + /// is . + /// + /// This method is implemented by using deferred execution. The immediate return value is an object + /// that stores all the information that is required to perform the action. + /// The query represented by this method is not executed until the object is enumerated by calling + /// its method. + /// + /// Two methods are defined to extend the type , which is the return type of this method. + /// These two methods, namely + /// and , enable you to specify additional + /// sort criteria to sort a sequence. + /// + /// and also return + /// an , which means any number of consecutive calls + /// to + /// or can be made. + /// + /// If comparer is , the default comparer is used to compare keys. + /// public static IOrderedEnumerable OrderDescending(this IEnumerable source, IComparer comparer) => new OrderedKeylessEnumerable(source, comparer, true, null); From b0fa0726c21a6aa4842dfa0fae0fffd9ae0d24ac Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Mon, 13 Jun 2022 20:17:12 +0200 Subject: [PATCH 04/17] Merge branch 'main' into issue-67194 From 1025faf47813949daf51566b5a268c78d37516db Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Mon, 13 Jun 2022 21:28:16 +0200 Subject: [PATCH 05/17] Add more test cases --- .../System.Linq/tests/OrderDescendingTests.cs | 169 ++++++- src/libraries/System.Linq/tests/OrderTests.cs | 455 +++++++++++++++++- 2 files changed, 613 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Linq/tests/OrderDescendingTests.cs b/src/libraries/System.Linq/tests/OrderDescendingTests.cs index e9bc6f84b5db4..2e77f6a9da189 100644 --- a/src/libraries/System.Linq/tests/OrderDescendingTests.cs +++ b/src/libraries/System.Linq/tests/OrderDescendingTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using Xunit; namespace System.Linq.Tests @@ -8,12 +9,22 @@ namespace System.Linq.Tests public sealed class OrderDescendingTests : EnumerableTests { [Fact] - public void Order() + public void SameResultsRepeatCallsIntQuery() { - int[] source = { 9, 1, 3, 2, 5, 4, 6, 7, 8, 0 }; - int[] expected = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + var q = from x1 in new int[] { 1, 6, 0, -1, 3 } + select x1; - Assert.Equal(expected, source.OrderDescending().ToArray()); + Assert.Equal(q.OrderDescending(), q.OrderDescending()); + } + + [Fact] + public void SameResultsRepeatCallsStringQuery() + { + var q = from x1 in new[] { "!@#$%^", "C", "AAA", "", null, "Calling Twice", "SoS", string.Empty } + where !string.IsNullOrEmpty(x1) + select x1; + + Assert.Equal(q.OrderDescending().ThenBy(f => f.Replace("C", "")), q.OrderDescending().ThenBy(f => f.Replace("C", ""))); } [Fact] @@ -24,10 +35,12 @@ public void SourceEmpty() } [Fact] - public void OrderedCount() + public void KeySelectorReturnsNull() { - var source = Enumerable.Range(0, 20).Shuffle(); - Assert.Equal(20, source.OrderDescending().Count()); + int?[] source = { null, null, null }; + int?[] expected = { null, null, null }; + + Assert.Equal(expected, source.OrderDescending()); } [Fact] @@ -38,5 +51,147 @@ public void ElementsAllSameKey() Assert.Equal(expected, source.OrderDescending()); } + + [Fact] + public void KeySelectorCalled() + { + var source = new[] + { + 90, 45, 0, 99 + }; + var expected = new[] + { + 99, 90, 45, 0 + }; + + Assert.Equal(expected, source.OrderDescending(null)); + } + + [Fact] + public void FirstAndLastAreDuplicatesCustomComparer() + { + string[] source = { "Prakash", "Alpha", "DAN", "dan", "Prakash" }; + string[] expected = { "Prakash", "Prakash", "DAN", "dan", "Alpha" }; + + Assert.Equal(expected, source.OrderDescending(StringComparer.OrdinalIgnoreCase)); + } + + [Fact] + public void RunOnce() + { + string[] source = { "Prakash", "Alpha", "DAN", "dan", "Prakash" }; + string[] expected = { "Prakash", "Prakash", "DAN", "dan", "Alpha" }; + + Assert.Equal(expected, source.RunOnce().OrderDescending(StringComparer.OrdinalIgnoreCase)); + } + + [Fact] + public void FirstAndLastAreDuplicatesNullPassedAsComparer() + { + int[] source = { 5, 1, 3, 2, 5 }; + int[] expected = { 5, 5, 3, 2, 1 }; + + Assert.Equal(expected, source.OrderDescending(null)); + } + + [Fact] + public void SourceReverseOfResultNullPassedAsComparer() + { + int[] source = { -75, -50, 0, 5, 9, 30, 100 }; + int[] expected = { 100, 30, 9, 5, 0, -50, -75 }; + + Assert.Equal(expected, source.OrderDescending(null)); + } + + [Fact] + public void SameKeysVerifySortStable() + { + var source = new[] + { + 90, 45, 0, 99 + }; + var expected = new[] + { + 99, 90, 45, 0 + }; + + Assert.Equal(expected, source.OrderDescending()); + } + + private class ExtremeComparer : IComparer + { + public int Compare(int x, int y) + { + if (x == y) + return 0; + if (x < y) + return int.MinValue; + return int.MaxValue; + } + } + + [Fact] + public void OrderByExtremeComparer() + { + int[] outOfOrder = new[] { 7, 1, 0, 9, 3, 5, 4, 2, 8, 6 }; + + // The .NET Framework has a bug where the input is incorrectly ordered if the comparer + // returns int.MaxValue or int.MinValue. See https://github.com/dotnet/corefx/pull/2240. + IEnumerable ordered = outOfOrder.OrderDescending(new ExtremeComparer()).ToArray(); + Assert.Equal(Enumerable.Range(0, 10).Reverse(), ordered); + } + + [Fact] + public void NullSource() + { + IEnumerable source = null; + AssertExtensions.Throws("source", () => source.OrderDescending()); + } + + [Fact] + public void SortsLargeAscendingEnumerableCorrectly() + { + const int Items = 1_000_000; + IEnumerable expected = NumberRangeGuaranteedNotCollectionType(0, Items).Reverse(); + + IEnumerable unordered = expected.Select(i => i); + IOrderedEnumerable ordered = unordered.OrderDescending(); + + Assert.Equal(expected, ordered); + } + + [Fact] + public void SortsLargeDescendingEnumerableCorrectly() + { + const int Items = 1_000_000; + IEnumerable expected = NumberRangeGuaranteedNotCollectionType(0, Items).Reverse(); + + IEnumerable unordered = expected.Select(i => Items - i - 1); + IOrderedEnumerable ordered = unordered.OrderDescending(); + + Assert.Equal(expected, ordered); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(8)] + [InlineData(16)] + [InlineData(1024)] + [InlineData(4096)] + [InlineData(1_000_000)] + public void SortsRandomizedEnumerableCorrectly(int items) + { + var r = new Random(42); + + int[] randomized = Enumerable.Range(0, items).Select(i => r.Next()).ToArray(); + int[] ordered = ForceNotCollection(randomized).OrderDescending().ToArray(); + + Array.Sort(randomized, (a, b) => a - b); + Array.Reverse(randomized); + Assert.Equal(randomized, ordered); + } } } diff --git a/src/libraries/System.Linq/tests/OrderTests.cs b/src/libraries/System.Linq/tests/OrderTests.cs index 3027853440629..6258f06c8b67e 100644 --- a/src/libraries/System.Linq/tests/OrderTests.cs +++ b/src/libraries/System.Linq/tests/OrderTests.cs @@ -1,19 +1,38 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Globalization; +using System.Tests; using Xunit; namespace System.Linq.Tests { public sealed class OrderTests : EnumerableTests { + private class BadComparer1 : IComparer + { + public int Compare(int x, int y) + { + return 1; + } + } + + private class BadComparer2 : IComparer + { + public int Compare(int x, int y) + { + return -1; + } + } + [Fact] - public void Order() + public void SameResultsRepeatCallsIntQuery() { - int[] source = { 9, 1, 3, 2, 5, 4, 6, 7, 8, 0 }; - int[] expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + var q = from x1 in new int[] { 1, 6, 0, -1, 3 } + select x1; - Assert.Equal(expected, source.Order().ToArray()); + Assert.Equal(q.Order().ThenBy(f => f * 2), q.Order().ThenBy(f => f * 2)); } [Fact] @@ -30,6 +49,25 @@ public void OrderedCount() Assert.Equal(20, source.Order().Count()); } + //FIXME: This will hang with a larger source. Do we want to deal with that case? + [Fact] + public void SurviveBadComparerAlwaysReturnsNegative() + { + int[] source = { 1 }; + int[] expected = { 1 }; + + Assert.Equal(expected, source.Order(new BadComparer2())); + } + + [Fact] + public void KeySelectorReturnsNull() + { + int?[] source = { null, null, null }; + int?[] expected = { null, null, null }; + + Assert.Equal(expected, source.Order()); + } + [Fact] public void ElementsAllSameKey() { @@ -38,5 +76,414 @@ public void ElementsAllSameKey() Assert.Equal(expected, source.Order()); } + + [Fact] + public void FirstAndLastAreDuplicatesCustomComparer() + { + string[] source = { "Prakash", "Alpha", "dan", "DAN", "Prakash" }; + string[] expected = { "Alpha", "dan", "DAN", "Prakash", "Prakash" }; + + Assert.Equal(expected, source.Order(StringComparer.OrdinalIgnoreCase)); + } + + [Fact] + public void RunOnce() + { + string[] source = { "Prakash", "Alpha", "dan", "DAN", "Prakash" }; + string[] expected = { "Alpha", "dan", "DAN", "Prakash", "Prakash" }; + + Assert.Equal(expected, source.RunOnce().Order(StringComparer.OrdinalIgnoreCase)); + } + + [Fact] + public void FirstAndLastAreDuplicatesNullPassedAsComparer() + { + int[] source = { 5, 1, 3, 2, 5 }; + int[] expected = { 1, 2, 3, 5, 5 }; + + Assert.Equal(expected, source.Order(null)); + } + + [Fact] + public void SourceReverseOfResultNullPassedAsComparer() + { + int?[] source = { 100, 30, 9, 5, 0, -50, -75, null }; + int?[] expected = { null, -75, -50, 0, 5, 9, 30, 100 }; + + Assert.Equal(expected, source.Order(null)); + } + + [Fact] + public void OrderedToArray() + { + var source = new[] + { + 5, 9, 6, 7, 8, 5, 20 + }; + var expected = new[] + { + 5, 5, 6, 7, 8, 9, 20 + }; + + Assert.Equal(expected, source.Order().ToArray()); + } + + [Fact] + public void EmptyOrderedToArray() + { + Assert.Empty(Enumerable.Empty().Order().ToArray()); + } + + [Fact] + public void OrderedToList() + { + var source = new[] + { + 5, 9, 6, 7, 8, 5, 20 + }; + var expected = new[] + { + 5, 5, 6, 7, 8, 9, 20 + }; + + Assert.Equal(expected, source.Order().ToList()); + } + + [Fact] + public void EmptyOrderedToList() + { + Assert.Empty(Enumerable.Empty().Order().ToList()); + } + + //FIXME: This will hang with a larger source. Do we want to deal with that case? + [Fact] + public void SurviveBadComparerAlwaysReturnsPositive() + { + int[] source = { 1 }; + int[] expected = { 1 }; + + Assert.Equal(expected, source.Order(new BadComparer1())); + } + + private class ExtremeComparer : IComparer + { + public int Compare(int x, int y) + { + if (x == y) + return 0; + if (x < y) + return int.MinValue; + return int.MaxValue; + } + } + + [Fact] + public void OrderExtremeComparer() + { + var outOfOrder = new[] { 7, 1, 0, 9, 3, 5, 4, 2, 8, 6 }; + Assert.Equal(Enumerable.Range(0, 10), outOfOrder.Order(new ExtremeComparer())); + } + + [Fact] + public void NullSource() + { + IEnumerable source = null; + AssertExtensions.Throws("source", () => source.Order()); + } + + [Fact] + public void FirstOnOrdered() + { + Assert.Equal(0, Enumerable.Range(0, 10).Shuffle().Order().First()); + Assert.Equal(9, Enumerable.Range(0, 10).Shuffle().OrderDescending().First()); + } + + [Fact] + public void FirstOnEmptyOrderedThrows() + { + Assert.Throws(() => Enumerable.Empty().Order().First()); + } + + [Fact] + public void FirstWithPredicateOnOrdered() + { + IEnumerable ordered = Enumerable.Range(0, 10).Shuffle().Order(); + IEnumerable orderedDescending = Enumerable.Range(0, 10).Shuffle().OrderDescending(); + int counter; + + counter = 0; + Assert.Equal(0, ordered.First(i => { counter++; return true; })); + Assert.Equal(1, counter); + + counter = 0; + Assert.Equal(9, ordered.First(i => { counter++; return i == 9; })); + Assert.Equal(10, counter); + + counter = 0; + Assert.Throws(() => ordered.First(i => { counter++; return false; })); + Assert.Equal(10, counter); + + counter = 0; + Assert.Equal(9, orderedDescending.First(i => { counter++; return true; })); + Assert.Equal(1, counter); + + counter = 0; + Assert.Equal(0, orderedDescending.First(i => { counter++; return i == 0; })); + Assert.Equal(10, counter); + + counter = 0; + Assert.Throws(() => orderedDescending.First(i => { counter++; return false; })); + Assert.Equal(10, counter); + } + + [Fact] + public void FirstOrDefaultOnOrdered() + { + Assert.Equal(0, Enumerable.Range(0, 10).Shuffle().Order().FirstOrDefault()); + Assert.Equal(9, Enumerable.Range(0, 10).Shuffle().OrderDescending().FirstOrDefault()); + Assert.Equal(0, Enumerable.Empty().Order().FirstOrDefault()); + } + + [Fact] + public void FirstOrDefaultWithPredicateOnOrdered() + { + IEnumerable Order = Enumerable.Range(0, 10).Shuffle().Order(); + IEnumerable OrderDescending = Enumerable.Range(0, 10).Shuffle().OrderDescending(); + int counter; + + counter = 0; + Assert.Equal(0, Order.FirstOrDefault(i => { counter++; return true; })); + Assert.Equal(1, counter); + + counter = 0; + Assert.Equal(9, Order.FirstOrDefault(i => { counter++; return i == 9; })); + Assert.Equal(10, counter); + + counter = 0; + Assert.Equal(0, Order.FirstOrDefault(i => { counter++; return false; })); + Assert.Equal(10, counter); + + counter = 0; + Assert.Equal(9, OrderDescending.FirstOrDefault(i => { counter++; return true; })); + Assert.Equal(1, counter); + + counter = 0; + Assert.Equal(0, OrderDescending.FirstOrDefault(i => { counter++; return i == 0; })); + Assert.Equal(10, counter); + + counter = 0; + Assert.Equal(0, OrderDescending.FirstOrDefault(i => { counter++; return false; })); + Assert.Equal(10, counter); + } + + [Fact] + public void LastOnOrdered() + { + Assert.Equal(9, Enumerable.Range(0, 10).Shuffle().Order().Last()); + Assert.Equal(0, Enumerable.Range(0, 10).Shuffle().OrderDescending().Last()); + } + + [Fact] + public void LastOnOrderedMatchingCases() + { + object[] boxedInts = new object[] { 0, 1, 2, 9, 1, 2, 3, 9, 4, 5, 7, 8, 9, 0, 1 }; + Assert.Same(boxedInts[12], boxedInts.Order().Last()); + Assert.Same(boxedInts[12], boxedInts.Order().LastOrDefault()); + Assert.Same(boxedInts[12], boxedInts.Order().Last(o => (int)o % 2 == 1)); + Assert.Same(boxedInts[12], boxedInts.Order().LastOrDefault(o => (int)o % 2 == 1)); + } + + [Fact] + public void LastOnEmptyOrderedThrows() + { + Assert.Throws(() => Enumerable.Empty().Order().Last()); + } + + [Fact] + public void LastOrDefaultOnOrdered() + { + Assert.Equal(9, Enumerable.Range(0, 10).Shuffle().Order().LastOrDefault()); + Assert.Equal(0, Enumerable.Range(0, 10).Shuffle().OrderDescending().LastOrDefault()); + Assert.Equal(0, Enumerable.Empty().Order().LastOrDefault()); + } + + [Fact] + public void EnumeratorDoesntContinue() + { + var enumerator = NumberRangeGuaranteedNotCollectionType(0, 3).Shuffle().Order().GetEnumerator(); + while (enumerator.MoveNext()) { } + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void SortsLargeAscendingEnumerableCorrectly() + { + const int Items = 1_000_000; + IEnumerable expected = NumberRangeGuaranteedNotCollectionType(0, Items); + + IEnumerable unordered = expected.Select(i => i); + IOrderedEnumerable ordered = unordered.Order(); + + Assert.Equal(expected, ordered); + } + + [Fact] + public void SortsLargeDescendingEnumerableCorrectly() + { + const int Items = 1_000_000; + IEnumerable expected = NumberRangeGuaranteedNotCollectionType(0, Items); + + IEnumerable unordered = expected.Select(i => Items - i - 1); + IOrderedEnumerable ordered = unordered.Order(); + + Assert.Equal(expected, ordered); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(8)] + [InlineData(16)] + [InlineData(1024)] + [InlineData(4096)] + [InlineData(1_000_000)] + public void SortsRandomizedEnumerableCorrectly(int items) + { + var r = new Random(42); + + int[] randomized = Enumerable.Range(0, items).Select(i => r.Next()).ToArray(); + int[] ordered = ForceNotCollection(randomized).Order().ToArray(); + + Array.Sort(randomized); + Assert.Equal(randomized, ordered); + } + + [Theory] + [InlineData(new[] { 1 })] + [InlineData(new[] { 1, 2 })] + [InlineData(new[] { 2, 1 })] + [InlineData(new[] { 1, 2, 3, 4, 5 })] + [InlineData(new[] { 5, 4, 3, 2, 1 })] + [InlineData(new[] { 4, 3, 2, 1, 5, 9, 8, 7, 6 })] + [InlineData(new[] { 2, 4, 6, 8, 10, 5, 3, 7, 1, 9 })] + public void TakeOne(IEnumerable source) + { + int count = 0; + foreach (int x in source.Order().Take(1)) + { + count++; + Assert.Equal(source.Min(), x); + } + Assert.Equal(1, count); + } + + [Fact] + public void CultureOrder() + { + string[] source = new[] { "Apple0", "Æble0", "Apple1", "Æble1", "Apple2", "Æble2" }; + + CultureInfo dk = new CultureInfo("da-DK"); + CultureInfo au = new CultureInfo("en-AU"); + + StringComparer comparerDk = StringComparer.Create(dk, ignoreCase: false); + StringComparer comparerAu = StringComparer.Create(au, ignoreCase: false); + + // we don't provide a defined sorted result set because the Windows culture sorting + // provides a different result set to the Linux culture sorting. But as we're really just + // concerned that Order default string ordering matches current culture then this + // should be sufficient + string[] resultDK = source.ToArray(); + Array.Sort(resultDK, comparerDk); + string[] resultAU = source.ToArray(); + Array.Sort(resultAU, comparerAu); + + string[] check; + + using (new ThreadCultureChange(dk)) + { + check = source.Order().ToArray(); + Assert.Equal(resultDK, check, StringComparer.Ordinal); + } + + using (new ThreadCultureChange(au)) + { + check = source.Order().ToArray(); + Assert.Equal(resultAU, check, StringComparer.Ordinal); + } + + using (new ThreadCultureChange(dk)) // "dk" whilst GetEnumerator + { + IEnumerator s = source.Order().GetEnumerator(); + using (new ThreadCultureChange(au)) // but "au" whilst accessing... + { + int idx = 0; + while (s.MoveNext()) // sort is done on first MoveNext, so should have "au" sorting + { + Assert.Equal(resultAU[idx++], s.Current, StringComparer.Ordinal); + } + } + } + + using (new ThreadCultureChange(au)) + { + // "au" whilst GetEnumerator + IEnumerator s = source.Order().GetEnumerator(); + + using (new ThreadCultureChange(dk)) + { + // but "dk" on first MoveNext + bool moveNext = s.MoveNext(); + Assert.True(moveNext); + + // ensure changing culture after MoveNext doesn't affect sort + using (new ThreadCultureChange(au)) // "au" whilst GetEnumerator + { + int idx = 0; + while (moveNext) // sort is done on first MoveNext, so should have "dk" sorting + { + Assert.Equal(resultDK[idx++], s.Current, StringComparer.Ordinal); + moveNext = s.MoveNext(); + } + } + } + } + } + + [Fact] + public void CultureOrderElementAt() + { + string[] source = new[] { "Apple0", "Æble0", "Apple1", "Æble1", "Apple2", "Æble2" }; + + CultureInfo dk = new CultureInfo("da-DK"); + CultureInfo au = new CultureInfo("en-AU"); + + StringComparer comparerDk = StringComparer.Create(dk, ignoreCase: false); + StringComparer comparerAu = StringComparer.Create(au, ignoreCase: false); + + // we don't provide a defined sorted result set because the Windows culture sorting + // provides a different result set to the Linux culture sorting. But as we're really just + // concerned that Order default string ordering matches current culture then this + // should be sufficient + string[] resultDK = source.ToArray(); + Array.Sort(resultDK, comparerDk); + string[] resultAU = source.ToArray(); + Array.Sort(resultAU, comparerAu); + + IEnumerable delaySortedSource = source.Order(); + for (int i = 0; i < source.Length; ++i) + { + using (new ThreadCultureChange(dk)) + { + Assert.Equal(resultDK[i], delaySortedSource.ElementAt(i), StringComparer.Ordinal); + } + + using (new ThreadCultureChange(au)) + { + Assert.Equal(resultAU[i], delaySortedSource.ElementAt(i), StringComparer.Ordinal); + } + } + } } } From 16bfbf9af21a5b868dbdc3938bc89424f1994cc2 Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Mon, 13 Jun 2022 21:41:46 +0200 Subject: [PATCH 06/17] Apply suggestions Co-Authored-By: Eirik Tsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> --- .../System.Linq/src/System/Linq/OrderBy.cs | 48 ++----------------- 1 file changed, 4 insertions(+), 44 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index 0cf341912e02b..b23982a1b3f94 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -20,17 +20,7 @@ public static partial class Enumerable /// The query represented by this method is not executed until the object is enumerated by calling /// its method. /// - /// Two methods are defined to extend the type , which is the return type of this method. - /// These two methods, namely - /// and , enable you to specify additional - /// sort criteria to sort a sequence. - /// - /// and also return - /// an , which means any number of consecutive calls - /// to - /// or can be made. - /// - /// This method compares keys by using the default comparer . + /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable Order(this IEnumerable source) => new OrderedKeylessEnumerable(source, null, false, null); @@ -49,17 +39,7 @@ public static IOrderedEnumerable Order(this IEnumerable source) => /// The query represented by this method is not executed until the object is enumerated by calling /// its method. /// - /// Two methods are defined to extend the type , which is the return type of this method. - /// These two methods, namely - /// and , enable you to specify additional - /// sort criteria to sort a sequence. - /// - /// and also return - /// an , which means any number of consecutive calls - /// to - /// or can be made. - /// - /// If comparer is , the default comparer is used to compare keys. + /// If comparer is , the default comparer is used to compare elements. /// public static IOrderedEnumerable Order(this IEnumerable source, IComparer comparer) => new OrderedKeylessEnumerable(source, comparer, false, null); @@ -83,17 +63,7 @@ public static IOrderedEnumerable OrderBy(this IEnumerabl /// The query represented by this method is not executed until the object is enumerated by calling /// its method. /// - /// Two methods are defined to extend the type , which is the return type of this method. - /// These two methods, namely - /// and , enable you to specify additional - /// sort criteria to sort a sequence. - /// - /// and also return - /// an , which means any number of consecutive calls - /// to - /// or can be made. - /// - /// This method compares keys by using the default comparer . + /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable OrderDescending(this IEnumerable source) => new OrderedKeylessEnumerable(source, null, true, null); @@ -112,17 +82,7 @@ public static IOrderedEnumerable OrderDescending(this IEnumerable sourc /// The query represented by this method is not executed until the object is enumerated by calling /// its method. /// - /// Two methods are defined to extend the type , which is the return type of this method. - /// These two methods, namely - /// and , enable you to specify additional - /// sort criteria to sort a sequence. - /// - /// and also return - /// an , which means any number of consecutive calls - /// to - /// or can be made. - /// - /// If comparer is , the default comparer is used to compare keys. + /// If comparer is , the default comparer is used to compare elements. /// public static IOrderedEnumerable OrderDescending(this IEnumerable source, IComparer comparer) => new OrderedKeylessEnumerable(source, comparer, true, null); From 6a21dbf9ffa5d9fb0adb931b4fad97256e0565ee Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Mon, 13 Jun 2022 22:33:46 +0200 Subject: [PATCH 07/17] Remove unneccesary keyless stuff --- .../System.Linq/src/System/Linq/OrderBy.cs | 8 +- .../src/System/Linq/OrderedEnumerable.cs | 113 ------------------ 2 files changed, 4 insertions(+), 117 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index b23982a1b3f94..f513a0c3e2f22 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -23,7 +23,7 @@ public static partial class Enumerable /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable Order(this IEnumerable source) => - new OrderedKeylessEnumerable(source, null, false, null); + OrderBy(source, static element => element); /// /// Sorts the elements of a sequence in ascending order. @@ -42,7 +42,7 @@ 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) => - new OrderedKeylessEnumerable(source, comparer, false, null); + OrderBy(source, static element => element, comparer); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) => new OrderedEnumerable(source, keySelector, null, false, null); @@ -66,7 +66,7 @@ public static IOrderedEnumerable OrderBy(this IEnumerabl /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable OrderDescending(this IEnumerable source) => - new OrderedKeylessEnumerable(source, null, true, null); + OrderByDescending(source, static element => element); /// /// Sorts the elements of a sequence in descending order. @@ -85,7 +85,7 @@ 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) => - new OrderedKeylessEnumerable(source, comparer, true, null); + OrderByDescending(source, static element => element, comparer); public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) => new OrderedEnumerable(source, keySelector, null, true, null); diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs index 5095eaeebef41..2c8cf01f941d6 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -106,53 +106,6 @@ IOrderedEnumerable IOrderedEnumerable.CreateOrderedEnumerabl } } - internal sealed class OrderedKeylessEnumerable : OrderedEnumerable - { - private readonly OrderedEnumerable? _parent; - private readonly IComparer _comparer; - private readonly bool _descending; - - internal OrderedKeylessEnumerable(IEnumerable source, IComparer? comparer, bool descending, OrderedEnumerable? parent) : base(source) - { - if (source is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - - _parent = parent; - _comparer = comparer ?? Comparer.Default; - _descending = descending; - } - - internal override EnumerableSorter GetEnumerableSorter(EnumerableSorter? next) - { - // Special case the common use of string with default comparer. Comparer.Default checks the - // thread's Culture on each call which is an overhead which is not required, because we are about to - // do a sort which remains on the current thread (and EnumerableSorter is not used afterwards). - IComparer comparer = _comparer; - if (typeof(TElement) == typeof(string) && comparer == Comparer.Default) - { - comparer = (IComparer)StringComparer.CurrentCulture; - } - - EnumerableSorter sorter = new EnumerableSorter(static item => item, comparer, _descending, next); - if (_parent != null) - { - sorter = _parent.GetEnumerableSorter(sorter); - } - - return sorter; - } - - internal override CachingComparer GetComparer(CachingComparer? childComparer) - { - CachingComparer cmp = childComparer == null - ? new KeylessCachingComparer(_comparer, _descending) - : new KeylessCachingComparerWithChild(_comparer, _descending, childComparer); - return _parent != null ? _parent.GetComparer(cmp) : cmp; - } - } - internal sealed class OrderedEnumerable : OrderedEnumerable { private readonly OrderedEnumerable? _parent; @@ -249,35 +202,6 @@ internal override void SetElement(TElement element) } } - internal sealed class KeylessCachingComparer : CachingComparer - { - private readonly IComparer _comparer; - private readonly bool _descending; - private TElement? _lastElement; - - public KeylessCachingComparer(IComparer comparer, bool descending) - { - _comparer = comparer; - _descending = descending; - } - - internal override int Compare(TElement element, bool cacheLower) - { - int cmp = _descending ? _comparer.Compare(_lastElement, element) : _comparer.Compare(element, _lastElement); - if (cacheLower == cmp < 0) - { - _lastElement = element; - } - - return cmp; - } - - internal override void SetElement(TElement element) - { - _lastElement = element; - } - } - internal sealed class CachingComparerWithChild : CachingComparer { private readonly CachingComparer _child; @@ -313,43 +237,6 @@ internal override void SetElement(TElement element) } } - internal sealed class KeylessCachingComparerWithChild : CachingComparer - { - private readonly IComparer _comparer; - private readonly bool _descending; - private readonly CachingComparer _child; - private TElement? _lastElement; - - public KeylessCachingComparerWithChild(IComparer comparer, bool descending, CachingComparer child) - { - _comparer = comparer; - _descending = descending; - _child = child; - } - - internal override int Compare(TElement element, bool cacheLower) - { - int cmp = _descending ? _comparer.Compare(_lastElement, element) : _comparer.Compare(element, _lastElement); - if (cmp == 0) - { - return _child.Compare(element, cacheLower); - } - - if (cacheLower == cmp < 0) - { - _lastElement = element; - _child.SetElement(element); - } - - return cmp; - } - - internal override void SetElement(TElement element) - { - _lastElement = element; - } - } - internal abstract class EnumerableSorter { internal abstract void ComputeKeys(TElement[] elements, int count); From fbb3a74dbceac78ca3c6f4646b7d3891f264e6aa Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Tue, 14 Jun 2022 16:26:10 +0200 Subject: [PATCH 08/17] Add more Queryable test cases --- .../tests/OrderDescendingTests.cs | 33 ++++++++++++++++++- .../System.Linq.Queryable/tests/OrderTests.cs | 33 ++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs b/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs index 9a5bc5524a629..aa30e2d81b98e 100644 --- a/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs @@ -2,13 +2,44 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Linq; using Xunit; namespace System.Linq.Tests { public sealed class OrderDescendingTests : EnumerableBasedTests { + [Fact] + public void FirstAndLastAreDuplicatesCustomComparer() + { + string[] source = { "Prakash", "Alpha", "DAN", "dan", "Prakash" }; + string[] expected = { "Prakash", "Prakash", "DAN", "dan", "Alpha" }; + + Assert.Equal(expected, source.AsQueryable().OrderDescending(StringComparer.OrdinalIgnoreCase)); + } + + [Fact] + public void FirstAndLastAreDuplicatesNullPassedAsComparer() + { + int[] source = { 5, 1, 3, 2, 5 }; + int[] expected = { 5, 5, 3, 2, 1 }; + + Assert.Equal(expected, source.AsQueryable().OrderDescending(null)); + } + + [Fact] + public void NullSource() + { + IQueryable source = null; + AssertExtensions.Throws("source", () => source.OrderDescending()); + } + + [Fact] + public void NullSourceComparer() + { + IQueryable source = null; + AssertExtensions.Throws("source", () => source.OrderDescending(Comparer.Default)); + } + [Fact] public void OrderDescending1() { diff --git a/src/libraries/System.Linq.Queryable/tests/OrderTests.cs b/src/libraries/System.Linq.Queryable/tests/OrderTests.cs index a2efe2ed0c5ad..7cf69256d7ca7 100644 --- a/src/libraries/System.Linq.Queryable/tests/OrderTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/OrderTests.cs @@ -2,13 +2,44 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Linq; using Xunit; namespace System.Linq.Tests { public sealed class OrderTests : EnumerableBasedTests { + [Fact] + public void FirstAndLastAreDuplicatesCustomComparer() + { + string[] source = { "Prakash", "Alpha", "dan", "DAN", "Prakash" }; + string[] expected = { "Alpha", "dan", "DAN", "Prakash", "Prakash" }; + + Assert.Equal(expected, source.AsQueryable().Order(StringComparer.OrdinalIgnoreCase)); + } + + [Fact] + public void FirstAndLastAreDuplicatesNullPassedAsComparer() + { + int[] source = { 5, 1, 3, 2, 5 }; + int[] expected = { 1, 2, 3, 5, 5 }; + + Assert.Equal(expected, source.AsQueryable().Order(null)); + } + + [Fact] + public void NullSource() + { + IQueryable source = null; + AssertExtensions.Throws("source", () => source.Order()); + } + + [Fact] + public void NullSourceComparer() + { + IQueryable source = null; + AssertExtensions.Throws("source", () => source.Order(Comparer.Default)); + } + [Fact] public void Order1() { From 879421b9a5e6d0fba02f0b50cd253eba16f53259 Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Tue, 14 Jun 2022 16:32:38 +0200 Subject: [PATCH 09/17] Eliminate `keySelector` null-check in ctor --- .../System.Linq/src/System/Linq/OrderBy.cs | 30 ++++++++++++++----- .../src/System/Linq/OrderedEnumerable.cs | 4 --- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index f513a0c3e2f22..1b399c9d1b2fc 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -23,7 +23,7 @@ public static partial class Enumerable /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable Order(this IEnumerable source) => - OrderBy(source, static element => element); + new OrderedEnumerable(source, static element => element, null, false, null); /// /// Sorts the elements of a sequence in ascending order. @@ -42,7 +42,7 @@ 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, static element => element, comparer); + new OrderedEnumerable(source, static element => element, comparer, false, null); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) => new OrderedEnumerable(source, keySelector, null, false, null); @@ -66,7 +66,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, static element => element); + new OrderedEnumerable(source, static element => element, null, true, null); /// /// Sorts the elements of a sequence in descending order. @@ -85,13 +85,27 @@ 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, static element => element, comparer); + new OrderedEnumerable(source, static element => element, comparer, true, null); - public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) => - new OrderedEnumerable(source, keySelector, null, true, null); + public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) + { + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } + + return new OrderedEnumerable(source, keySelector, null, true, null); + } - public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector, IComparer? comparer) => - new OrderedEnumerable(source, keySelector, comparer, true, null); + public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector, IComparer? comparer) + { + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } + + return new OrderedEnumerable(source, keySelector, comparer, true, null); + } public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector) { diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs index 2c8cf01f941d6..3daf80fa42d0b 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -120,10 +120,6 @@ internal OrderedEnumerable(IEnumerable source, Func ke { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } _parent = parent; _keySelector = keySelector; From 7f2c3f1a4173d15c6fc187cb937585bcd7d9f082 Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Tue, 14 Jun 2022 21:25:51 +0200 Subject: [PATCH 10/17] Add null checks to `ThenBy` --- .../System.Linq/src/System/Linq/OrderBy.cs | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index 1b399c9d1b2fc..17a470e281f1c 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -44,11 +44,25 @@ public static IOrderedEnumerable Order(this IEnumerable source) => public static IOrderedEnumerable Order(this IEnumerable source, IComparer comparer) => new OrderedEnumerable(source, static element => element, comparer, false, null); - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) => - new OrderedEnumerable(source, keySelector, null, false, null); + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) + { + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) => - new OrderedEnumerable(source, keySelector, comparer, false, null); + return new OrderedEnumerable(source, keySelector, null, false, null); + } + + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) + { + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } + + return new OrderedEnumerable(source, keySelector, comparer, false, null); + } /// /// Sorts the elements of a sequence in descending order. @@ -113,6 +127,10 @@ public static IOrderedEnumerable ThenBy(this IOrderedEnu { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } return source.CreateOrderedEnumerable(keySelector, null, false); } @@ -123,6 +141,10 @@ public static IOrderedEnumerable ThenBy(this IOrderedEnu { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } return source.CreateOrderedEnumerable(keySelector, comparer, false); } @@ -133,6 +155,10 @@ public static IOrderedEnumerable ThenByDescending(this I { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } return source.CreateOrderedEnumerable(keySelector, null, true); } @@ -143,6 +169,10 @@ public static IOrderedEnumerable ThenByDescending(this I { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } return source.CreateOrderedEnumerable(keySelector, comparer, true); } From 1382af84ba4bf34ae6390a8b07d3374be0a5281d Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Tue, 28 Jun 2022 18:51:30 +0200 Subject: [PATCH 11/17] Revert "Eliminate `keySelector` null-check in ctor" This reverts commit 879421b9a5e6d0fba02f0b50cd253eba16f53259. --- .../System.Linq/src/System/Linq/OrderBy.cs | 30 +++++-------------- .../src/System/Linq/OrderedEnumerable.cs | 4 +++ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index 17a470e281f1c..55657e836d382 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -23,7 +23,7 @@ public static partial class Enumerable /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable Order(this IEnumerable source) => - new OrderedEnumerable(source, static element => element, null, false, null); + OrderBy(source, static element => element); /// /// Sorts the elements of a sequence in ascending order. @@ -42,7 +42,7 @@ 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) => - new OrderedEnumerable(source, static element => element, comparer, false, null); + OrderBy(source, static element => element, comparer); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) { @@ -80,7 +80,7 @@ public static IOrderedEnumerable OrderBy(this IEnumerabl /// This method compares elements by using the default comparer . /// public static IOrderedEnumerable OrderDescending(this IEnumerable source) => - new OrderedEnumerable(source, static element => element, null, true, null); + OrderByDescending(source, static element => element); /// /// Sorts the elements of a sequence in descending order. @@ -99,27 +99,13 @@ 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) => - new OrderedEnumerable(source, static element => element, comparer, true, null); + OrderByDescending(source, static element => element, comparer); - public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) - { - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return new OrderedEnumerable(source, keySelector, null, true, null); - } + public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) => + new OrderedEnumerable(source, keySelector, null, true, null); - public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector, IComparer? comparer) - { - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return new OrderedEnumerable(source, keySelector, comparer, true, null); - } + public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector, IComparer? comparer) => + new OrderedEnumerable(source, keySelector, comparer, true, null); public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector) { diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs index 3daf80fa42d0b..2c8cf01f941d6 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -120,6 +120,10 @@ internal OrderedEnumerable(IEnumerable source, Func ke { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } _parent = parent; _keySelector = keySelector; From bd1659aa83f0da998c6baf8afea23be31a76328c Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Tue, 28 Jun 2022 18:51:57 +0200 Subject: [PATCH 12/17] Add tests for CreateOrderedEnumerable --- .../tests/CreateOrderedEnumerableTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/libraries/System.Linq/tests/CreateOrderedEnumerableTests.cs diff --git a/src/libraries/System.Linq/tests/CreateOrderedEnumerableTests.cs b/src/libraries/System.Linq/tests/CreateOrderedEnumerableTests.cs new file mode 100644 index 0000000000000..820a533a5e475 --- /dev/null +++ b/src/libraries/System.Linq/tests/CreateOrderedEnumerableTests.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Linq.Tests +{ + public sealed class CreateOrderedEnumerableTests + { + [Fact] + public void ThrowsNullKeySelector() + { + var enumerable = new int[0].Order(); + Assert.Throws(() => enumerable.CreateOrderedEnumerable((Func)null!, null, false)); + } + } +} From 45a3776c08d23b3ee818386281499e9f7f80e6db Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Wed, 29 Jun 2022 21:40:56 +0200 Subject: [PATCH 13/17] Apply suggestions --- .../System.Linq/src/System/Linq/OrderBy.cs | 70 ++----------------- 1 file changed, 6 insertions(+), 64 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index 55657e836d382..cfc4029407785 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -45,24 +45,10 @@ public static IOrderedEnumerable Order(this IEnumerable source, ICompar OrderBy(source, static element => element, comparer); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) - { - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return new OrderedEnumerable(source, keySelector, null, false, null); - } + => OrderBy(source, keySelector, null); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) - { - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return new OrderedEnumerable(source, keySelector, comparer, false, null); - } + => new OrderedEnumerable(source, keySelector, comparer, false, null); /// /// Sorts the elements of a sequence in descending order. @@ -108,60 +94,16 @@ public static IOrderedEnumerable OrderByDescending(this new OrderedEnumerable(source, keySelector, comparer, true, null); public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector) - { - if (source == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return source.CreateOrderedEnumerable(keySelector, null, false); - } + => source.CreateOrderedEnumerable(keySelector, null, false); public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer? comparer) - { - if (source == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return source.CreateOrderedEnumerable(keySelector, comparer, false); - } + => source.CreateOrderedEnumerable(keySelector, comparer, false); public static IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, Func keySelector) - { - if (source == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return source.CreateOrderedEnumerable(keySelector, null, true); - } + => source.CreateOrderedEnumerable(keySelector, null, true); public static IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, Func keySelector, IComparer? comparer) - { - if (source == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - - return source.CreateOrderedEnumerable(keySelector, comparer, true); - } + => source.CreateOrderedEnumerable(keySelector, comparer, true); } public interface IOrderedEnumerable : IEnumerable From 94369ff26ca5c50b906ea866e862996a62eccd9e Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Wed, 29 Jun 2022 21:58:54 +0200 Subject: [PATCH 14/17] Fix null checks --- .../System.Linq/src/System/Linq/OrderBy.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index cfc4029407785..4893bb9d8b421 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -94,16 +94,44 @@ public static IOrderedEnumerable OrderByDescending(this new OrderedEnumerable(source, keySelector, comparer, true, null); public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector) - => source.CreateOrderedEnumerable(keySelector, null, false); + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + return source.CreateOrderedEnumerable(keySelector, null, false); + } public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer? comparer) - => source.CreateOrderedEnumerable(keySelector, comparer, false); + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + return source.CreateOrderedEnumerable(keySelector, comparer, false); + } public static IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, Func keySelector) - => source.CreateOrderedEnumerable(keySelector, null, true); + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + return source.CreateOrderedEnumerable(keySelector, null, true); + } public static IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, Func keySelector, IComparer? comparer) - => source.CreateOrderedEnumerable(keySelector, comparer, true); + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + return source.CreateOrderedEnumerable(keySelector, comparer, false); + } } public interface IOrderedEnumerable : IEnumerable From abddd4da080cb8caed4879c9ebee640077d78d08 Mon Sep 17 00:00:00 2001 From: Robin Lindner Date: Wed, 29 Jun 2022 21:59:39 +0200 Subject: [PATCH 15/17] Fix null checks --- src/libraries/System.Linq/src/System/Linq/OrderBy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index 4893bb9d8b421..2a2601facf252 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -130,7 +130,7 @@ public static IOrderedEnumerable ThenByDescending(this I ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - return source.CreateOrderedEnumerable(keySelector, comparer, false); + return source.CreateOrderedEnumerable(keySelector, comparer, true); } } From c0eec68b82612dc9c726943711c6b1423a4b5f21 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 29 Jun 2022 23:05:54 +0100 Subject: [PATCH 16/17] Update src/libraries/System.Linq/src/System/Linq/OrderBy.cs --- src/libraries/System.Linq/src/System/Linq/OrderBy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index 2a2601facf252..e2adbb758c939 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -45,7 +45,7 @@ public static IOrderedEnumerable Order(this IEnumerable source, ICompar OrderBy(source, static element => element, comparer); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) - => OrderBy(source, keySelector, null); + new OrderedEnumerable(source, keySelector, null, false, null); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) => new OrderedEnumerable(source, keySelector, comparer, false, null); From 8b40b2fb66f5ff106272b7b1e3e956358b36e126 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 30 Jun 2022 12:27:18 +0100 Subject: [PATCH 17/17] Update src/libraries/System.Linq/src/System/Linq/OrderBy.cs --- src/libraries/System.Linq/src/System/Linq/OrderBy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs index e2adbb758c939..c0bddf796f2b0 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -45,7 +45,7 @@ public static IOrderedEnumerable Order(this IEnumerable source, ICompar OrderBy(source, static element => element, comparer); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) - new OrderedEnumerable(source, keySelector, null, false, null); + => new OrderedEnumerable(source, keySelector, null, false, null); public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer) => new OrderedEnumerable(source, keySelector, comparer, false, null);