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..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,81 @@ 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) + { + ArgumentNullException.ThrowIfNull(source); + + return (IOrderedQueryable)source.Provider.CreateQuery( + Expression.Call( + null, + CachedReflectionInfo.Order_T_1(typeof(T)), + source.Expression + )); + } + + /// + /// 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) + { + 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 +344,81 @@ 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) + { + ArgumentNullException.ThrowIfNull(source); + + return (IOrderedQueryable)source.Provider.CreateQuery( + Expression.Call( + null, + CachedReflectionInfo.OrderDescending_T_1(typeof(T)), + source.Expression + )); + } + + /// + /// 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) + { + 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..aa30e2d81b98e --- /dev/null +++ b/src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs @@ -0,0 +1,57 @@ +// 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 +{ + 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() + { + 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..7cf69256d7ca7 --- /dev/null +++ b/src/libraries/System.Linq.Queryable/tests/OrderTests.cs @@ -0,0 +1,57 @@ +// 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 +{ + 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() + { + 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(); 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..c0bddf796f2b0 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderBy.cs @@ -7,11 +7,85 @@ namespace System.Linq { public static partial class Enumerable { - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector) => - new OrderedEnumerable(source, keySelector, 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 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. + /// + /// This method compares elements by using the default comparer . + /// + public static IOrderedEnumerable Order(this IEnumerable source) => + OrderBy(source, static element => element); - 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 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. + /// + /// 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); + + 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); + + /// + /// 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. + /// + /// This method compares elements by using the default comparer . + /// + public static IOrderedEnumerable OrderDescending(this IEnumerable source) => + OrderByDescending(source, static element => element); + + /// + /// 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. + /// + /// 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); public static IOrderedEnumerable OrderByDescending(this IEnumerable source, Func keySelector) => new OrderedEnumerable(source, keySelector, null, true, null); 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)); + } + } +} diff --git a/src/libraries/System.Linq/tests/OrderDescendingTests.cs b/src/libraries/System.Linq/tests/OrderDescendingTests.cs new file mode 100644 index 0000000000000..2e77f6a9da189 --- /dev/null +++ b/src/libraries/System.Linq/tests/OrderDescendingTests.cs @@ -0,0 +1,197 @@ +// 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 +{ + public sealed class OrderDescendingTests : EnumerableTests + { + [Fact] + public void SameResultsRepeatCallsIntQuery() + { + var q = from x1 in new int[] { 1, 6, 0, -1, 3 } + select x1; + + 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] + public void SourceEmpty() + { + int[] source = { }; + Assert.Empty(source.OrderDescending()); + } + + [Fact] + public void KeySelectorReturnsNull() + { + int?[] source = { null, null, null }; + int?[] expected = { null, null, null }; + + Assert.Equal(expected, source.OrderDescending()); + } + + [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()); + } + + [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 new file mode 100644 index 0000000000000..6258f06c8b67e --- /dev/null +++ b/src/libraries/System.Linq/tests/OrderTests.cs @@ -0,0 +1,489 @@ +// 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 SameResultsRepeatCallsIntQuery() + { + var q = from x1 in new int[] { 1, 6, 0, -1, 3 } + select x1; + + Assert.Equal(q.Order().ThenBy(f => f * 2), q.Order().ThenBy(f => f * 2)); + } + + [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()); + } + + //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() + { + int?[] source = { 9, 9, 9, 9, 9, 9 }; + int?[] expected = { 9, 9, 9, 9, 9, 9 }; + + 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); + } + } + } + } +} 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 @@ + +