Skip to content

Commit

Permalink
[API Implementation]: Add Order and OrderDescending to `Enumerabl…
Browse files Browse the repository at this point in the history
…e` and `Queryable` (dotnet#70525)

* Implement proposal for `System.Linq`

* Implement proposal for `System.Linq.Queryable`

* Add documentation

* Merge branch 'main' into issue-67194

* Add more test cases

* Apply suggestions

Co-Authored-By: Eirik Tsarpalis <[email protected]>

* Remove unneccesary keyless stuff

* Add more Queryable test cases

* Eliminate `keySelector` null-check in ctor

* Add null checks to `ThenBy`

* Revert "Eliminate `keySelector` null-check in ctor"

This reverts commit 879421b.

* Add tests for CreateOrderedEnumerable

* Apply suggestions

* Fix null checks

* Fix null checks

* Update src/libraries/System.Linq/src/System/Linq/OrderBy.cs

* Update src/libraries/System.Linq/src/System/Linq/OrderBy.cs

Co-authored-by: Eirik Tsarpalis <[email protected]>
Co-authored-by: Eirik Tsarpalis <[email protected]>
  • Loading branch information
3 people authored Jun 30, 2022
1 parent bb36771 commit 436ee2e
Show file tree
Hide file tree
Showing 13 changed files with 1,082 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ public static partial class Queryable
public static System.Linq.IOrderedQueryable<TSource> OrderByDescending<TSource, TKey>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, TKey>> keySelector, System.Collections.Generic.IComparer<TKey>? comparer) { throw null; }
public static System.Linq.IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, TKey>> keySelector) { throw null; }
public static System.Linq.IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, TKey>> keySelector, System.Collections.Generic.IComparer<TKey>? comparer) { throw null; }
public static System.Linq.IOrderedQueryable<T> OrderDescending<T>(this System.Linq.IQueryable<T> source) { throw null; }
public static System.Linq.IOrderedQueryable<T> OrderDescending<T>(this System.Linq.IQueryable<T> source, System.Collections.Generic.IComparer<T> comparer) { throw null; }
public static System.Linq.IOrderedQueryable<T> Order<T>(this System.Linq.IQueryable<T> source) { throw null; }
public static System.Linq.IOrderedQueryable<T> Order<T>(this System.Linq.IQueryable<T> source, System.Collections.Generic.IComparer<T> comparer) { throw null; }
public static System.Linq.IQueryable<TSource> Prepend<TSource>(this System.Linq.IQueryable<TSource> source, TSource element) { throw null; }
public static System.Linq.IQueryable<TSource> Reverse<TSource>(this System.Linq.IQueryable<TSource> source) { throw null; }
public static System.Linq.IQueryable<TResult> SelectMany<TSource, TResult>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, System.Collections.Generic.IEnumerable<TResult>>> selector) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,12 +530,36 @@ public static MethodInfo OfType_TResult_1(Type TResult) =>
(s_OfType_TResult_1 ??= new Func<IQueryable, IQueryable<object>>(Queryable.OfType<object>).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<IQueryable<object>, IOrderedQueryable<object>>(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<IQueryable<object>, IComparer<object>, IOrderedQueryable<object>>(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<IQueryable<object>, Expression<Func<object, object>>, IOrderedQueryable<object>>(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<IQueryable<object>, IOrderedQueryable<object>>(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<IQueryable<object>, IComparer<object>, IOrderedQueryable<object>>(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) =>
Expand Down
150 changes: 150 additions & 0 deletions src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,81 @@ public static IQueryable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(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<TKey>))));
}

/// <summary>
/// Sorts the elements of a sequence in ascending order.
/// </summary>
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
/// <param name="source">A sequence of values to order.</param>
/// <returns>An <see cref="IOrderedEnumerable{TElement}"/> whose elements are sorted.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method has at least one parameter of type <see cref="Expression{TDelegate}"/> whose type argument is one
/// of the <see cref="Func{T,TResult}"/> types.
/// For these parameters, you can pass in a lambda expression and it will be compiled to an <see cref="Expression{TDelegate}"/>.
///
/// The <see cref="Order{T}(IQueryable{T})"/> method generates a <see cref="MethodCallExpression"/> that represents
/// calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/> itself as a constructed generic method.
/// It then passes the <see cref="MethodCallExpression"/> to the <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> method
/// of the <see cref="IQueryProvider"/> represented by the <see cref="IQueryable.Provider"/> property of the <paramref name="source"/>
/// parameter. The result of calling <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> is cast to
/// type <see cref="IOrderedQueryable{T}"/> and returned.
///
/// The query behavior that occurs as a result of executing an expression tree
/// that represents calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/>
/// depends on the implementation of the <paramref name="source"/> parameter.
/// The expected behavior is that it sorts the elements of <paramref name="source"/> by itself.
/// </remarks>
[DynamicDependency("Order`1", typeof(Enumerable))]
public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source)
{
ArgumentNullException.ThrowIfNull(source);

return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(
Expression.Call(
null,
CachedReflectionInfo.Order_T_1(typeof(T)),
source.Expression
));
}

/// <summary>
/// Sorts the elements of a sequence in ascending order.
/// </summary>
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
/// <param name="source">A sequence of values to order.</param>
/// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
/// <returns>An <see cref="IOrderedEnumerable{TElement}"/> whose elements are sorted.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method has at least one parameter of type <see cref="Expression{TDelegate}"/> whose type argument is one
/// of the <see cref="Func{T,TResult}"/> types.
/// For these parameters, you can pass in a lambda expression and it will be compiled to an <see cref="Expression{TDelegate}"/>.
///
/// The <see cref="Order{T}(IQueryable{T})"/> method generates a <see cref="MethodCallExpression"/> that represents
/// calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/> itself as a constructed generic method.
/// It then passes the <see cref="MethodCallExpression"/> to the <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> method
/// of the <see cref="IQueryProvider"/> represented by the <see cref="IQueryable.Provider"/> property of the <paramref name="source"/>
/// parameter. The result of calling <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> is cast to
/// type <see cref="IOrderedQueryable{T}"/> and returned.
///
/// The query behavior that occurs as a result of executing an expression tree
/// that represents calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/>
/// depends on the implementation of the <paramref name="source"/> parameter.
/// The expected behavior is that it sorts the elements of <paramref name="source"/> by itself.
/// </remarks>
[DynamicDependency("Order`1", typeof(Enumerable))]
public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source, IComparer<T> comparer)
{
ArgumentNullException.ThrowIfNull(source);

return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(
Expression.Call(
null,
CachedReflectionInfo.Order_T_2(typeof(T)),
source.Expression, Expression.Constant(comparer, typeof(IComparer<T>))
));
}

[DynamicDependency("OrderBy`2", typeof(Enumerable))]
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
Expand Down Expand Up @@ -269,6 +344,81 @@ public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<
));
}

/// <summary>
/// Sorts the elements of a sequence in descending order.
/// </summary>
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
/// <param name="source">A sequence of values to order.</param>
/// <returns>An <see cref="IOrderedEnumerable{TElement}"/> whose elements are sorted.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method has at least one parameter of type <see cref="Expression{TDelegate}"/> whose type argument is one
/// of the <see cref="Func{T,TResult}"/> types.
/// For these parameters, you can pass in a lambda expression and it will be compiled to an <see cref="Expression{TDelegate}"/>.
///
/// The <see cref="Order{T}(IQueryable{T})"/> method generates a <see cref="MethodCallExpression"/> that represents
/// calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/> itself as a constructed generic method.
/// It then passes the <see cref="MethodCallExpression"/> to the <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> method
/// of the <see cref="IQueryProvider"/> represented by the <see cref="IQueryable.Provider"/> property of the <paramref name="source"/>
/// parameter. The result of calling <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> is cast to
/// type <see cref="IOrderedQueryable{T}"/> and returned.
///
/// The query behavior that occurs as a result of executing an expression tree
/// that represents calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/>
/// depends on the implementation of the <paramref name="source"/> parameter.
/// The expected behavior is that it sorts the elements of <paramref name="source"/> by itself.
/// </remarks>
[DynamicDependency("OrderDescending`1", typeof(Enumerable))]
public static IOrderedQueryable<T> OrderDescending<T>(this IQueryable<T> source)
{
ArgumentNullException.ThrowIfNull(source);

return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(
Expression.Call(
null,
CachedReflectionInfo.OrderDescending_T_1(typeof(T)),
source.Expression
));
}

/// <summary>
/// Sorts the elements of a sequence in descending order.
/// </summary>
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
/// <param name="source">A sequence of values to order.</param>
/// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
/// <returns>An <see cref="IOrderedEnumerable{TElement}"/> whose elements are sorted.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method has at least one parameter of type <see cref="Expression{TDelegate}"/> whose type argument is one
/// of the <see cref="Func{T,TResult}"/> types.
/// For these parameters, you can pass in a lambda expression and it will be compiled to an <see cref="Expression{TDelegate}"/>.
///
/// The <see cref="Order{T}(IQueryable{T})"/> method generates a <see cref="MethodCallExpression"/> that represents
/// calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/> itself as a constructed generic method.
/// It then passes the <see cref="MethodCallExpression"/> to the <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> method
/// of the <see cref="IQueryProvider"/> represented by the <see cref="IQueryable.Provider"/> property of the <paramref name="source"/>
/// parameter. The result of calling <see cref="IQueryProvider.CreateQuery{TElement}(Expression)"/> is cast to
/// type <see cref="IOrderedQueryable{T}"/> and returned.
///
/// The query behavior that occurs as a result of executing an expression tree
/// that represents calling <see cref="Enumerable.Order{T}(IEnumerable{T})"/>
/// depends on the implementation of the <paramref name="source"/> parameter.
/// The expected behavior is that it sorts the elements of <paramref name="source"/> by itself.
/// </remarks>
[DynamicDependency("OrderDescending`1", typeof(Enumerable))]
public static IOrderedQueryable<T> OrderDescending<T>(this IQueryable<T> source, IComparer<T> comparer)
{
ArgumentNullException.ThrowIfNull(source);

return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(
Expression.Call(
null,
CachedReflectionInfo.OrderDescending_T_2(typeof(T)),
source.Expression, Expression.Constant(comparer, typeof(IComparer<T>))
));
}

[DynamicDependency("OrderByDescending`2", typeof(Enumerable))]
public static IOrderedQueryable<TSource> OrderByDescending<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
Expand Down
57 changes: 57 additions & 0 deletions src/libraries/System.Linq.Queryable/tests/OrderDescendingTests.cs
Original file line number Diff line number Diff line change
@@ -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<int> source = null;
AssertExtensions.Throws<ArgumentNullException>("source", () => source.OrderDescending());
}

[Fact]
public void NullSourceComparer()
{
IQueryable<int> source = null;
AssertExtensions.Throws<ArgumentNullException>("source", () => source.OrderDescending(Comparer<int>.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<int>.Default).Count();
Assert.Equal(3, count);
}
}
}
57 changes: 57 additions & 0 deletions src/libraries/System.Linq.Queryable/tests/OrderTests.cs
Original file line number Diff line number Diff line change
@@ -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<int> source = null;
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Order());
}

[Fact]
public void NullSourceComparer()
{
IQueryable<int> source = null;
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Order(Comparer<int>.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<int>.Default).Count();
Assert.Equal(3, count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
<Compile Include="OfTypeTests.cs" />
<Compile Include="OrderByDescendingTests.cs" />
<Compile Include="OrderByTests.cs" />
<Compile Include="OrderDescendingTests.cs" />
<Compile Include="OrderTests.cs" />
<Compile Include="Queryable.cs" />
<Compile Include="QueryFromExpressionTests.cs" />
<Compile Include="ReverseTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 436ee2e

Please sign in to comment.