Skip to content

[API Proposal]: Add some LINQ extension to return a single element but not throw for multiple #119209

@LWChris

Description

@LWChris

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Linq

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions