-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
When we are interested in whether there is exactly one element in an IEnumeable<T>
(with or without a predicate), there's three possible outcomes:
- No, n=0
- Yes, n=1
- No, n>1
LINQ does currently not provide an easy way to "retrieve if n=1, default otherwise" without the need to handle exceptions: FirstOrDefault
, LastOrDefault
and SingleOrDefault
all return default(T)
for n=0, and the element for n=1. But for n> 1, the first two also return an element, and the latter throws an InvalidOperationException
.
I have made the same request 6 years ago (#29398) as UniqueOrDefault
, but it was closed, because Eirik Tsarpalis didn't like the name "Unique". He felt it implies the return value is a boolean, like "All" or "Any". I didn't agree; I'd name such a function "IsUnique". He didn't reply to that for a month, then just closed the issue.
Well, I don't insist on any name in particular, but I still need the functionality quite often and find myself adding a custom extension to many projects. Please re-consider adding it, under a name that you see fit.
API Proposal
namespace System.Collections.Generic;
public partial static class Enumerable
{
public static T? OnlyOrDefault<T>(this IEnumerable<T> sequence);
public static T? OnlyOrDefault<T>(this IEnumerable<T> sequence, Func<T, bool> predicate);
}
API Usage
private static bool TryGetAutoCompleteSuggestion(this IEnumerable<string> entries, string input, out string? suggestion) {
suggestion = entries.OnlyOrDefault(e => e.StartsWith(input));
return suggestion != null;
}
var selectedOption = availableOptions.OnlyOrDefault() // If there's only one option applicable, use that,
?? availableOptions.OnlyOrDefault(o => o.IsDefault) // otherwise select default option if unambiguous,
?? ShowOptionSelectDialog(availableOptions); // otherwise let the user pick one.
Alternative Designs
namespace System.Collections.Generic;
public enum Cardinality
{
Empty, // or Zero, None
Single, // or One, Only
Many // or Multiple
}
public partial static class Enumerable
{
public static bool TryGetOnly<T>(this IEnumerable<T> sequence, out T? element);
public static bool TryGetOnly<T>(this IEnumerable<T> sequence, out T? element, out Cardinality cardinality);
public static bool TryGetOnly<T>(this IEnumerable<T> sequence, Func<T, bool> predicate, out T? element);
public static bool TryGetOnly<T>(this IEnumerable<T> sequence, Func<T, bool> predicate, out T? element, out Cardinality cardinality);
}
This is fitting the "TryGet" pattern, but it wouldn't fit that well alongside [First|Last|Single](OrDefault)?
Risks
None.