Skip to content

Allow for specify return value on System.Linq.Enumerable.*OrDefault methods #20064

Closed

Description

@Keboo

I would like to propose adding overloads to the System.Linq.Enumerable.*OrDefault and System.Linq.Queryable.*OrDefault methods for specifying the value to be returned in the default case. This is the value that would be returned instead of default(TSource) when there are no items in the enumeration

There are times when the default value of a given type may be a valid value from the enumeration. If the default value for the enumeration type is valid value, there is not a nice way to determine if the returned values was because the enumeration was empty or if that value was in the enumeration.

Proposed API change

namespace System.Linq
{
    public static class Enumerable
    {
        public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue);
        public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, TSource defaultValue);

        public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue);
        public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, TSource defaultValue);

        public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue);
        public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, TSource defaultValue);
    }

    public static class Queryable
    {
        public static TSource SingleOrDefault<TSource>(this IQueryable<TSource> source, TSource defaultValue);
        public static TSource SingleOrDefault<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate, TSource defaultValue);

        public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, TSource defaultValue);
        public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate, TSource defaultValue);

        public static TSource LastOrDefault<TSource>(this IQueryable<TSource> source, TSource defaultValue);
        public static TSource LastOrDefault<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate, TSource defaultValue);
    }
}

Updates

  • Switch from using default parameters to method overloads
  • Added IQueryable methods as well

Activity

  1. jnm2 commented on Jan 31, 2017

    @jnm2
    Contributor

    This would be an alternative to having to switch from SingleOrDefault(predicate) to DefaultIfEmpty(x).Single(predicate).

  2. karelz commented on Feb 1, 2017

    @karelz
    Member

    You are adding new methods (we can't change existing ones).
    I think the = default(TSource) will cause compiler ambiguity with the existing methods - we need to drop the default value IMO (please check first if I am not mistaken).

  3. JonHanna commented on Feb 1, 2017

    @JonHanna
    Contributor

    It wouldn't cause ambiguity at the compiler level, but the default would never be used because the existing methods would always beat it in the overload resolution, so it's pointless. It could certainly cause ambiguity at the human understanding level.

    If we had this (I've no strong opinion either way on that) we should also have Queryable equivalents.

  4. jnm2 commented on Feb 1, 2017

    @jnm2
    Contributor

    I believe the way these conversations usually go is that you can't use optional parameters without breaking binary back compat. http://haacked.com/archive/2010/08/10/versioning-issues-with-optional-arguments.aspx

  5. JonHanna commented on Feb 1, 2017

    @JonHanna
    Contributor

    I think it would be safe in that regard, given the types involved, (though it could break someone reflecting and using the count of parameters to find which overload they wanted), but I wouldn't bet on that, and it could still mess with some future change, and in any case they're still pointless defaults as they'll never be used.

  6. Keboo commented on Feb 1, 2017

    @Keboo
    MemberAuthor

    @karelz and @JonHanna I updated the issue description based on your comments (I think I did it correctly; first time filing an issue in this repo).

  7. karelz commented on Feb 1, 2017

    @karelz
    Member

    @Keboo all good so far, we will let you know if you're not following "the rules" ;-)

    FYI: Next steps in API review process are:

    1. Feedback from community on API addition
    2. Area owners support the API addition as good & valuable and tag it as 'api-ready-for-review'
    3. API review group reviews the API and tags it as 'api-approved' (or sends it back to 'api-needs-work' with comments

    Don't expect super-fast turnaround. New APIs need bake time and some thinking through. The process should not be rushed even for obvious things.

    If you are eager to contribute to CoreFX, I would recommend to grab some 'up for grabs' issues (which are not API proposals) in the meantime - the most valuable/impactful things are:

    1. 2.0.0 (next release) up for grabs issues
    2. wishlist issues
    3. simple issues for newbie contributors (not applied too much yet - we just started experimenting with it recently)
  8. karelz commented on Feb 1, 2017

    @karelz
    Member

    @jnm2 AFAIK the proposal only adds APIs, it of course can't remove any ;-)

  9. jnm2 commented on Feb 1, 2017

    @jnm2
    Contributor

    @karelz Ah, misunderstood the purpose of the original proposal. Latest revision makes sense.

  10. jamesqo commented on Feb 1, 2017

    @jamesqo
    Contributor

    Would it be better to have a selector run for the default value instead of a concrete value?

    public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource> selectorIfEmpty);

    I wonder if we could also pass in more information to the SingleOrDefault selector, like whether the enumerable was empty or had more than 1 element.

  11. Keboo commented on Feb 2, 2017

    @Keboo
    MemberAuthor

    @jamesqo I am not sure the selector adds much value here. Can you elaborate on when you would find it useful?
    As for the SingleOrDefault case, it sounds like you are expecting this selector to get invoked in both when the enumeration is empty and when it contains multiple items. Wouldn't this be a change in the behavior of SingleOrDefault?

  12. MarkMichaelis commented on Feb 2, 2017

    @MarkMichaelis

    This makes sense to me... good suggestion @Keboo.

  13. jamesqo commented on Feb 5, 2017

    @jamesqo
    Contributor

    @Keboo Sorry for the late reply.

    I am not sure the selector adds much value here. Can you elaborate on when you would find it useful?

    I was thinking it could be used to lazily load a value if there was no item matching the predicate. Looking back though, I agree it may not make much sense, mainly because Linq is used with pure functions so a argument-less function would go against the grain.

    @karelz Everyone seems to think this is a good idea, so maybe it can be marked ready-for-review. (I have another idea for these methods which I plan to open a proposal for later, but I won't block this on it.)

  14. karelz commented on Feb 5, 2017

    @karelz
    Member

    @jamesqo area experts are supposed to make the first-level API approval, by marking the API as such: @VSadov @OmarTawfik can you please check if the API proposal is in line with Linq direction?

  15. OmarTawfik commented on Feb 7, 2017

    @OmarTawfik
    Contributor

    LGTM. @VSadov?
    I think it is useful addition to the API.

  16. 26 remaining items

  17. eiriktsarpalis commented on Nov 13, 2020

    @eiriktsarpalis
    Member

    If this change is approved, I would be happy to volunteer to implement this.

    Thanks for volunteering @Foxtrek64, I have assigned the issue to you.

  18. Joe4evr commented on Nov 13, 2020

    @Joe4evr
    Contributor

    @Foxtrek64 For reference, a similar method that has existed for ages in .NET is Nullable<T>.GetValueOrDefault(T defaultValue).

  19. GeraudFabien commented on Nov 13, 2020

    @GeraudFabien

    You could use the operator "??" wich is great because :

    • More readable when used to it
    • More flexible (Allow throw, other linq, ...)
    • More performent since it doesn't need to allocate the default
    • Doesn't need new method and use the current logic
    • Work with Queryable

    For any case where you have a struc like dictionary, int, datetime i use two trick depending of the case

    • When you want a nullable property in struct like KeyValuePair i just do '?.Key ?? throw new XXX;'.
    • Datetime and int without rule i juste move the rule in a where 'Where(x => x == z).OffType<datetime?>().FirstOrDefault()'. This way at most only one is encapsulate in Nullable<>

    I used to implement this overload for one reason. When you read "or default" i expect my first param to be default. With "??" the default is after the predicate wich can be long in some case and become hard to read.

    const string MostPopularName = "Dotnet";
    string foo = nameRows.FirstOrDefault(MostPopularName, nameRow =>
                            nameRow.State == MyEnumOfState.MyState &&
                            !bannedWords.Contains(nameRow) &&
                            someOtherRules);
    const string MostPopularName = "Dotnet";
    string foo = nameRows.FirstOrDefault(nameRow =>
                            nameRow.State == MyEnumOfState.MyState &&
                            !bannedWords.Contains(nameRow) &&
                            someOtherRules, 
                            MostPopularName);
    const string MostPopularName = "Dotnet";
    string foo = nameRows.FirstOrDefault(nameRow =>
                            nameRow.State == MyEnumOfState.MyState &&
                            !bannedWords.Contains(nameRow.Name) &&
                            someOtherRules)
                         ?.Name ?? MostPopularName;
  20. TheBrambleShark commented on Dec 7, 2020

    @TheBrambleShark
    Contributor

    A few questions-

    System.Collections.Immutable has its own LINQ library. Should I extend this, or any additional libraries, in this change?

    Should we add ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, int index, TSource other); to this change? It was not included in the original scope but I feel it is likely a logical addition.

  21. KieranDevvs commented on Jan 25, 2021

    @KieranDevvs

    You could use the operator "??" wich is great because :

    • More readable when used to it
    • More flexible (Allow throw, other linq, ...)
    • More performent since it doesn't need to allocate the default
    • Doesn't need new method and use the current logic
    • Work with Queryable

    For any case where you have a struc like dictionary, int, datetime i use two trick depending of the case

    • When you want a nullable property in struct like KeyValuePair i just do '?.Key ?? throw new XXX;'.
    • Datetime and int without rule i juste move the rule in a where 'Where(x => x == z).OffType<datetime?>().FirstOrDefault()'. This way at most only one is encapsulate in Nullable<>

    I used to implement this overload for one reason. When you read "or default" i expect my first param to be default. With "??" the default is after the predicate wich can be long in some case and become hard to read.

    const string MostPopularName = "Dotnet";
    string foo = nameRows.FirstOrDefault(MostPopularName, nameRow =>
                            nameRow.State == MyEnumOfState.MyState &&
                            !bannedWords.Contains(nameRow) &&
                            someOtherRules);
    const string MostPopularName = "Dotnet";
    string foo = nameRows.FirstOrDefault(nameRow =>
                            nameRow.State == MyEnumOfState.MyState &&
                            !bannedWords.Contains(nameRow) &&
                            someOtherRules, 
                            MostPopularName);
    const string MostPopularName = "Dotnet";
    string foo = nameRows.FirstOrDefault(nameRow =>
                            nameRow.State == MyEnumOfState.MyState &&
                            !bannedWords.Contains(nameRow.Name) &&
                            someOtherRules)
                         ?.Name ?? MostPopularName;

    That wouldn't work, here's why:
    Take this example:

    var source = new int[] {1, 2, 3};
    
    // Compiler error, cant null coalesce non nullable type.
    int item = source.FirstOrDefault() ?? 5;

    In this scenario, you would have to change the collection data type to allow nullable values, and in that case, you don't know if null is a valid value from the query, or if its not found anything.

    For example:

    var source = new int?[] {null, 1, 2, 3};
    int item = source.FirstOrDefault() ?? 5;

    If the source first item is null, FirstOrDefault returns the item (null) and then the coalesce returns 5.
    If the source is empty, FirstOrDefault returns default(T) of int? which is null, and the coalesce returns 5.
    In other words, this isn't an alternative solution to the problem because there is no way to determine between default(T) and a valid value.

  22. eiriktsarpalis commented on Jan 26, 2021

    @eiriktsarpalis
    Member

    Hi @Foxtrek64, per your question you should just stick to the approved APIs for now. Consider creating a new issue if you feel that it could also be added to immutable collections.

  23. modified the milestones: Future, 6.0.0 on Jan 28, 2021
  24. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Feb 28, 2021
  25. added a commit that references this issue on Mar 17, 2021
    b261537
  26. added a commit that references this issue on Mar 18, 2021
    122c438
  27. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Mar 18, 2021
  28. ghost locked as resolved and limited conversation to collaborators on Apr 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Linqhelp wanted[up-for-grabs] Good issue for external contributors

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions