Skip to content

[API Proposal]: Enumerable.Order and OrderDescending #67194

Closed

Description

@terrajobst

Background and motivation

Linq provides OrderBy and OrderByDescending which implies that I'm sorting based on some part of the T. Very often I find myself wanting to just sort by the T itself.

API Proposal

namespace System.Linq
{
    public partial class Enumerable
    {
        public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source) where T: IComparable<T>;
        public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source, IComparer<T> comparer);

        public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source) where T: IComparable<T>;
        public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source, IComparer<T> comparer);
    }
}

API Usage

var data = new[] { 2, 1, 3 };

// Instead of
// var sorted = data.OrderBy(e => e);

var sorted = data.Order();

// Instead of
// var sortedDescending = data.OrderByDescending(e => e);

var sortedDescending = data.OrderDescending();

Activity

  1. added
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Mar 26, 2022
  2. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Mar 26, 2022
  3. kevingosse commented on Mar 26, 2022

    @kevingosse
    Contributor

    Shouldn't it return an IOrderedEnumerable<T> for consistency with OrderBy?

  4. petroemil commented on Mar 26, 2022

    @petroemil

    Could they be called Order and OrderDescending instead, dropping the past tense "ed" from the names?

  5. stephentoub commented on Mar 26, 2022

    @stephentoub
    Member

    If we add this, I think the name should just be Order rather than Ordered.

  6. poke commented on Mar 26, 2022

    @poke
    Contributor

    Maybe something explicit to avoid confusion with the existing methods: OrderBySelf and OrderBySelfDescending (OrderByDescendingSelf?)

    Alternatively just OrderBy() without any argument, defaulting to an identity function? 🤔

  7. CamiloTerevinto commented on Mar 26, 2022

    @CamiloTerevinto

    I think it would make more sense to allow for any T too, just like the existing APIs:

        public partial class Enumerable
        {
            // source.OrderBy(x => x)
            public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source) where T: IComparable<T>;
    
            // source.OrderBy(x => x, comparer)
            public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source, IComparer<T> comparer);
    
            // source.OrderByDescending(x => x, comparer)
            public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source) where T: IComparable<T>;
    
            // source.OrderByDescending(x => x, comparer)
            public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source, IComparer<T> comparer)
        }
    
  8. zoran-horvat commented on Mar 26, 2022

    @zoran-horvat

    Could they be called Order and OrderDescending instead, dropping the past tense "ed" from the names?

    I agree, with a note that "Ordered" implies that the method will return data in some form, but does not communicate that there will be a substantial cost disproportional to size of the input. "Order" is suggesting that there is work behind constructing the result.

  9. ldardick commented on Mar 26, 2022

    @ldardick

    Could they be called Order and OrderDescending instead, dropping the past tense "ed" from the names?

    I agree, with a note that "Ordered" implies that the method will return data in some form, but does not communicate that there will be a substantial cost disproportional to size of the input. "Order" is suggesting that there is work behind constructing the result.

    Adding up, I feel explicitly defining the critetia (OrderAscending and OrderDescending) feels more natural.

  10. andi0b commented on Mar 26, 2022

    @andi0b

    So it more or less comes down to instead of calling a LINQ method with an identity function (x=>x) as parameter, just calling it without a parameter.

    There are also other LINQ methods that could use this API change. For example SelectMany(x=>x).

    But also ToDictionary(x=>x.key, x=>x.value) for a enumerable of tuples or KeyValuePairs (see #45679)

  11. terrajobst commented on Mar 26, 2022

    @terrajobst
    ContributorAuthor

    Updated proposal based on your feedback.

  12. changed the title [-][API Proposal]: Enumerable.Ordered and OrderedDescending[/-] [+][API Proposal]: Enumerable.Order and OrderDescending[/+] on Mar 26, 2022
  13. Joe4evr commented on Mar 26, 2022

    @Joe4evr
    Contributor

    There are also other LINQ methods that could use this API change. For example SelectMany(x=>x).

    That almost happened (settled on the name Flatten(), but it didn't make the cut for .NET 6: #54220).

  14. 16 remaining items

  15. added
    blockingMarks issues that we want to fast track in order to unblock other important work
    and removed
    blockingMarks issues that we want to fast track in order to unblock other important work
    on Jun 7, 2022
  16. bartonjs commented on Jun 9, 2022

    @bartonjs
    Member
    • Since this keeps coming up, we recommend first doing an analysis of Enumerable to see if we can do something that is consistent and not explosive, like making selector functions nullable (and defaulted to null).
      • We did the analysis on the call. Losing generic inference makes that a worse approach.
    • We removed the generic constraint both for consistency, and IEnumerable<int?>
    • Should also apply to Queryable
    namespace System.Linq
    {
        public partial class Enumerable
        {
            public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source);
            public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source, IComparer<T> comparer);
    
            public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source);
            public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source, IComparer<T> comparer);
        }
        public partial class Queryable
        {
            // Or whatever's correct.
            [DynamicDependency("Order`1", typeof(Enumerable))]
            public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source);
            [DynamicDependency("Order`1", typeof(Enumerable))]
            public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source, IComparer<T> comparer);
    
            [DynamicDependency("OrderDescending`1", typeof(Enumerable))]
            public static IOrderedQueryable<T> OrderDescending<T>(this IQueryable<T> source);
            [DynamicDependency("OrderDescending`1", typeof(Enumerable))]
            public static IOrderedQueryable<T> OrderDescending<T>(this IQueryable<T> source, IComparer<T> comparer);
        }
    }
  17. added
    api-approvedAPI was approved in API review, it can be implemented
    and removed
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    on Jun 9, 2022
  18. eiriktsarpalis commented on Jun 9, 2022

    @eiriktsarpalis
    Member

    Based on the decision made today, would it make sense to revisit Flatten() as well?

    #54220 (comment)

  19. stephentoub commented on Jun 9, 2022

    @stephentoub
    Member

    ...and there's the slippery slope. 5 minutes... that took even less time than I expected ;-)

  20. deeprobin commented on Jun 9, 2022

    @deeprobin
    Contributor

    Doesn't seem like a big change.
    If you are ready for a community contribution here, feel free to assign me.

  21. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Jun 9, 2022
  22. deeprobin commented on Jun 10, 2022

    @deeprobin
    Contributor

    @bartonjs

    I don't think there is no way queryable can implement this new API in a more performant way than enumerable now does, as there is no documented way to introspect the comparer and somehow handle that on a repository layer.

    In that case: is it useful to add the helper on Queryable?

    Originally posted by @rhuijben in #70525 (comment)

  23. svick commented on Jun 12, 2022

    @svick
    Member

    @deeprobin The problem is, if the method is not added to Queryable, what happens if you call queryable.Order()? That code would be valid and would call Enumerable.Order(), since IQueryable<T> inherits from IEnumerable<T>. But then the ordering would not become part of the query, but would be instead executed in memory, which is not what you want.

  24. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Jun 30, 2022
  25. ghost locked as resolved and limited conversation to collaborators on Jul 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions