-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow for specify return value on System.Linq.Enumerable.*OrDefault methods #20064
Comments
This would be an alternative to having to switch from |
You are adding new methods (we can't change existing ones). |
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 |
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 |
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. |
@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:
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:
|
@jnm2 AFAIK the proposal only adds APIs, it of course can't remove any ;-) |
@karelz Ah, misunderstood the purpose of the original proposal. Latest revision makes sense. |
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 |
@jamesqo I am not sure the selector adds much value here. Can you elaborate on when you would find it useful? |
This makes sense to me... good suggestion @Keboo. |
@Keboo Sorry for the late reply.
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.) |
@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? |
LGTM. @VSadov? |
This is a valuable addition, LGTM as proposed. @adamsitnik any objections to marking this for api review? |
@eiriktsarpalis I agree that the problem exists and it should be addressed. Example: IEnumerable<int> integers = SomehowGetACollectionOfIntegers();
int item = integers.FirstOrDefault();
if (item == 0) // was the first value a `0` or there was no values at all? However, I am not sure if passing a
nit: Perhaps - TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue);
+ TSource SingleOrGiven<TSource>(this IEnumerable<TSource> source, TSource given); Alternatives are:
bool TryFirst<TSource>(this IEnumerable<TSource> source, out TSource found); and could also be unclear what
IEnumerable<int> integers = SomehowGetACollectionOfIntegers();
var item = integers.FirstOrDefault(); // compiler error: did you mean (int) or (bool, int) @eiriktsarpalis what do you think? I would like to be well prepared for the API review. |
I am not sure why you would need any form of comparison here. For example, a naive implementation of public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue)
{
foreach (TSource element in source)
{
return element;
}
return defaultValue;
} |
You are right. I've missunderstood the idea. It's "give me X or use this" and most probably no @eiriktsarpalis what about the name? do you like the idea of changing |
Probably, |
@eiriktsarpalis good, then I've changed the label from |
All of the proposed methods end the chain. |
@Foxtrek64 I agree with you, @terrajobst what is the recommended way of updating the proposal? Should I edit the issue description or post an updated comment? |
Looks good as proposed, but there was a concern raised that the Queryable methods might have an impact on Linq-to-SQL, which would be good to verify (@ajcvickers do you have insight or contacts here?). namespace System.Linq
{
public static partial 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, Expression<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, Expression<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, Expression<Func<TSource, bool>> predicate, TSource defaultValue);
}
} |
@bartonjs This should've been caught (much) earlier, but there's one tiny change that needs to happen to these additions: The overloads in |
@Joe4evr Good catch. Since they were approved as "add |
@bartonjs I'm not sure if it's too late for this seeing as it's already been through review, but I would like to voice a strong disapproval for the If this change is approved, I would be happy to volunteer to implement this. |
Since this enhancement is "I want the existing *OrDefault method, but to change what it thinks the default is", it seems like a natural overload to me, rather than having Additionally, .NET has had the "OrDefault" suffix for longer than C# has had the I definitely don't see changing these members to OrElse passing review, and am skeptical that we'd use that suffix on new functionality on Enumerable or Queryable... or even entirely new types. |
Thanks for volunteering @Foxtrek64, I have assigned the issue to you. |
@Foxtrek64 For reference, a similar method that has existed for ages in .NET is |
You could use the operator "??" wich is great because :
For any case where you have a struc like dictionary, int, datetime i use two trick depending of the case
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; |
A few questions-
Should we add |
That wouldn't work, here's why: 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, |
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. |
…ethods (#48886) * Fix #20064 * Add API to ref assembly * Make overloads with defaultValue not nullable * Add unit tests, simplify implementation * Add LastOrDefault tests * Add Queryable tests * Additional tests. Reformatting TryGet Methods. * Update src/libraries/System.Linq.Queryable/tests/FirstOrDefaultTests.cs * Apply suggestions from code review Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com> * Fix ref methods * Further adjust nullability * Fix more nullables * fix failing tests * Restore coding style Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
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 enumerationThere 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
Updates
The text was updated successfully, but these errors were encountered: