dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.29k stars 4.74k forks source link

Add TrySingle<TSource>(IEnumerable<TSource>, out TSource) #16362

Open shmuelie opened 8 years ago

shmuelie commented 8 years ago

Motivation

In LINQ we have the Single and SingleOrDefault methods, but they don't tell you if it succeeded or not.

The problem with these methods is that if there is more than one element in source (or more than one matching element in case of predicate) they all throw a System.InvalidOperationException. That is fine if more than one element is an error or very rare. If more than one element is common or expected then this will cause major slow downs.

Additionaly, in cases of (example: int collections), default(int) is 0, which wouldn't tell the caller whether the operation succeeded (returning the item 0) or not (returning the default 0).

Proposal

namespace System.Linq
{
    public static class Enumerable
    {
        public static TSource Single<TSource>(this IEnumerable<TSource> source);
        public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
        public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source);
        public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
+       public static bool TrySingle<TSource>(this IEnumerable<TSource> source, out TSource element);
+       public static bool TrySingle<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);

        public static TSource First<TSource>(this IEnumerable<TSource> source);
        public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
        public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source);
        public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
+       public static bool TryFirst<TSource>(this IEnumerable<TSource> source, out TSource element);
+       public static bool TryFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);

        public static TSource Last<TSource>(this IEnumerable<TSource> source);
        public static TSource Last<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
        public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source);
        public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
+       public static bool TryLast<TSource>(this IEnumerable<TSource> source, out TSource element);
+       public static bool TryLast<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);

        public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index);
        public static TSource ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, int index);
+       public static bool TryElementAt<TSource>(this IEnumerable<TSource> source, int index, out TSource element);
    }

    public static class Queryable
    {
        public static TSource Single<TSource>(this IQueryable<TSource> source);
        public static TSource Single<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
        public static TSource SingleOrDefault<TSource>(this IQueryable<TSource> source);
        public static TSource SingleOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
+       public static (bool success, T value) TrySingle<T>(this IQueryable<T> source);
+       public static (bool success, T value) TrySingle<T>(this IQueryable<T> source, Expression<Func<TSource, bool>> predicate);
+       public static bool TrySingle<TSource>(this IQueryable<TSource> source, out TSource element);
+       public static bool TrySingle<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, out TSource element);

        public static TSource First<TSource>(this IQueryable<TSource> source);
        public static TSource First<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
        public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source);
        public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
+       public static (bool success, T value) TryFirst<T>(this IQueryable<T> source);
+       public static (bool success, T value) TryFirst<T>(this IQueryable<T> source, Expression<Func<TSource, bool>> predicate);
+       public static bool TryFirst<TSource>(this IQueryable<TSource> source, out TSource element);
+       public static bool TryFirst<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, out TSource element);

        public static TSource Last<TSource>(this IQueryable<TSource> source);
        public static TSource Last<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
        public static TSource LastOrDefault<TSource>(this IQueryable<TSource> source);
        public static TSource LastOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
+       public static (bool success, T value) TryLast<T>(this IQueryable<T> source);
+       public static (bool success, T value) TryLast<T>(this IQueryable<T> source, Expression<Func<TSource, bool>> predicate);
+       public static bool TryLast<TSource>(this IQueryable<TSource> source, out TSource element);
+       public static bool TryLast<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, out TSource element);

        public static TSource ElementAt<TSource>(this IQueryable<TSource> source, int index);
        public static TSource ElementAtOrDefault<TSource>(this IQueryable<TSource> source, int index);
+       public static (bool success, T value) TryElementAt<T>(this IQueryable<T> source, int index);
+       public static bool TryElementAt<TSource>(this IQueryable<TSource> source, int index, out TSource element);
    }
}

Original

I'd like to propose a new LINQ method: TrySingle. The idea is pretty much the same as the existing Single method except it does not throw an exception when there is not only a single item in the sequence.

To do this I made to modifications to the Single contract:

  1. First the method returns bool. true if the sequence contains exactly one item, false otherwise.
  2. I added an out parameter of type TSource. This will "hold" the single element on success. On failure it is filled with default(TSource).

I have found this method very useful in my own code and if accepted can quickly put together a pull request.

AlexRadch commented 7 years ago

@jamesqo As I understand, all possible solutions have many issues.

karelz commented 7 years ago

@jamesqo I need first comment from area experts - @OmarTawfik or @VSadov (who is OOF).

OmarTawfik commented 7 years ago

I agree with @stephentoub. I think that we should stick to the pattern of other helpers. I don't see it as Try+Verb+Noun, but as Try+OriginalOperationName.

If Single were instead GetSingle and SingleOrDefault were instead GetSingleOrDefault, then TryGetSingle would make sense. But they're not. The Try pattern is generally to simply prefix the operation with "Try" and have it return a bool with the actual result as an out parameter, so it seems we should do that here as well. Thus I vote for TrySingle. e.TrySingle is no more strange than e.Single, and there's value in having them be consistent with each other, more so I believe than breaking from the pattern and trying to clarify the name.

karelz commented 7 years ago

@OmarTawfik that has been already "decided". The last open question is -- should we add the Queryable companions or not?

OmarTawfik commented 7 years ago

@karelz that makes sense to me.

karelz commented 7 years ago

Are they direct copy of the Enumerable methods? Can you please update the proposal at the top?

divega commented 7 years ago

I am only now catching up with this discussion and I would still like to provide my input:

I can second many of @bartdesmet's comments here, and I agree with the reasoning that if we are going to have these methods on IQueryable the version that returns a Tuple (or ValueTuple) seems to be more appropriate that one that uses and output parameter.

But in fact I would also argue that methods that return a Tuple (or ValueTuple) are a better fit for LINQ's fluent compositional style in general, even on IEnumerable<T>.

The bool TryGetX(..., out x) pattern is and has always been a little awkward to use and kind of the opposite of the style used in LINQ. AFAIR there have been multiple attempts to extend the language to make the former easier/more natural and support for tuple syntax is one of those features.

It might help the whole discussion if we could arrange examples of how the "success" value returned by these operaros can be used when the operators appear nested in a query in both standard method and query comprehension syntax.

jamesqo commented 7 years ago

@divega

But in fact I would also argue that methods that return a Tuple (or ValueTuple) are a better fit for LINQ's fluent compositional style in general, even on IEnumerable<T>.

I agree. The best thing for these methods to do, actually, would be to return a T?; it's too bad that Nullable has an artificial struct constraint. Out parameters don't really mix well with other methods because Linq is all about lazy evaluation, even though it works in this case (First, Single, etc. are eagerly evaluated).

On the other hand, however, Try with out is more natural than using a ValueTuple when working with if statements:

if (e.TryGetFirst(out var first)) { ... }

(bool got, T first) = e.TryGetFirst();
if (got) { ... }

So unless someone has a better idea, it seems like out is the best choice we have right now.

AlexRadch commented 7 years ago

I would like to use methods with out parameters in public interface, and at the same time, I support to use tuples inside Queryable and providers . I think this is the most convenient solution. The only issue is how find corresponding methods in Enumerable. I see next ways to solve this issue:

  1. Add QueryableToEnumerableHelper that will rewrite tuple methods to Enumerable methods with out parameters. Something like next
    public static class QueryableToEnumerableHelper // or some other name
    {
    public Tuple<bool, T> TrySingle<T>(IEnumerable<T> source)
    {
        T item;
        bool result = Enumerable.TrySingle(source, out item);
        return Tuple.Create(result, item);
    }
    }

Disadvantage: we should search enumerable methods not only in Enumerable class but in QueryableToEnumerableHelper class also.

  1. Have public methods in Enumerable with tuple for each method with out parameters and hide such methods by [EditorBrowsable(EditorBrowsableState.Never)] attribute. It is like previous solution but helper methods are inside Enumerable class, not inside separated helper class.

Disadvantage: user can use such methods.

  1. Have internal methods in Enumerable with tuple for each method with out parameters. It is like previous solution but we need to find internal (not public) methods. Is it possible?

  2. Define rules for Queryable to rewrite internal Tuple methods to Enumerable methods with out parameters. This solution is most complex and have many disadvantages.

I suggest to use second solution as the same universal and convenient. Now I rewrote PR code https://github.com/dotnet/corefx/pull/14276 to see how it will be with second solution.

@divega and @bartdesmet what do you think about such suggestion?

AlexRadch commented 7 years ago

@jamesqo can next

(bool got, T first) = e.TryGetFirst();
if (got) { ... }

be rewritten as next in c# 7.0 or not?

if ((var tryFirst = e.TryGetFirst()).Item1) { tryFirst.Item2 }

But in any way it is bad code style.

AlexRadch commented 7 years ago

@divega @bartdesmet @karelz I wrote some my design suggestions in https://github.com/dotnet/corefx/issues/6073#issuecomment-266939214

divega commented 7 years ago

@AlexRadch I can't remember any precedent of LINQ operators that were designed to only be used at the top of a query. That is why I am still trying to understand if these operators could be used inside a query and how the usage would look like.

I also still believe there is a lot of value in having symmetry between the Enumerable and Queryable stories.

All in all if we cannot come up with something that works everywhere in the query and for both Enumerable and Queryable I would be inclined not to make these operators part of our core libraries. Applications are free to define them if they really need them (i.e. for those not so frequent cases in which the default value of TSource is not enough to indicate failure to retrieve a value).

A simple implementation that I would hesitate to productize, but that would probably work ok in many cases for existing IQueryable<TSource> providers looks reasonable and simple enough:

public static bool TryFirst<TSource>(
    IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate, 
    out TSource first)
{
    foreach(var element in source.Where(predicate)) // consider adding .Take(1)
    {
        first = element;
        return true;
    }
    first = default(TSource);
    return false;
}

Implementing a similar version of TrySingle() reminded me about Single()'s two failure modes (I see it already came up earlier in this thread) which make this one feels a bit weirder:

public static bool TrySingle<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate, 
    out TSource single)
{
    var firstFound = false;
    single= default(TSource);
    foreach (var element in source.Where(predicate)) // consider adding .Take(2)
    {
        if (firstFound)
        {
            return false;
        }
        else
        {
            single = element;
            firstFound = true;
        }
    }
    return firstFound;
}
AlexRadch commented 7 years ago

@divega Thanks for great answer! As I see you suggest to add implementation inside Queryable and call old API for drivers. It makes sense because Try methods are not deferred.

I will made such implementation in this PR.

divega commented 7 years ago

As I see you suggest to add implementation inside Queryable and call old API for drivers.

@AlexRadch Not at all! I did not intend to suggest that, sorry if I wasn't clear. I am saying that this is the kind of implementation application developers could include in their own applications if it really helps them save a few lines of code, and I am questioning the value of including any implementation these methods in the .NET BCL, for either IEnumerable<T> or IQueryable<T>

jamesqo commented 7 years ago

@divega

(i.e. for those not so frequent cases in which the default value of TSource is not enough to indicate failure to retrieve a value)

I don't think it's so infrequent. Consider any generic method that wants to get the first value of an enumerable-- there's no way to do it smoothly and performantly with the current APIs. IMO, I don't see a good reason to block this proposal.

I do have an idea, however. Perhaps this is a bit radical, but what if we introduced an Optional<T> type to System.Linq that we could return from these functions? It would implement the Maybe monad and could be used in Linq query comprehensions. It would be similar to Nullable, except without the artificial struct constraint.

namespace System.Linq
{
    public struct Optional<T>
    {
        public Optional(T value);

        public static Optional<T> Empty { get; }

        public bool HasValue { get; }
        public T Value { get; }

        public T GetValueOrDefault();
        public bool TryGetValue(out T value);

        public Optional<TResult> Select<TResult>(Func<T, TResult> selector);
        public Optional<TResult> SelectMany<TMiddle, TResult>(Func<T, Optional<TMiddle>> selector1, Func<T, TMiddle, Optional<TResult>> selector2);
    }
}

We could then return this from the TryGet* methods: (edit: This was meant to be Try*. Corrected.)

public static Optional<T> TryFirst<T>(this IEnumerable<T> source);
public static Optional<T> TryLast<T>(this IEnumerable<T> source);
public static Optional<T> TrySingle<T>(this IEnumerable<T> source);

// + the predicate overloads

and use it like this

Optional<string> GetFirstString<T>(IEnumerable<object> objects)
{
    // If `objects` is null, the selector is not run and Optional<string>.Empty is returned
    return objects.TryFirst().Select(o => o.ToString());
}

// People who like the `out` pattern can still use it
if (ints.TryFirst().TryGetValue(out i))
{
    Console.WriteLine(i);
}
else
{
    Console.WriteLine("No ints found.");
}

// Compatible with query comprehensions

class Person { Optional<Person> Parent; }

Person person = ...
var greatGrandparent = from parent in person.Parent
                       from grandparent in parent.Parent
                       select grandparent.Parent;
divega commented 7 years ago

I don't think it's so infrequent.

@jamesqo My statements were intended to be subjective :smile: I don't have the means to accurately measure how often it is necessary to discriminate between a default value and a non-existent value. I just made an educated guess that needing this is less frequent than not needing it, and I am also making the assumption that people can figure out other patterns when they actually do need it. Those patterns may be a bit more complicated than the existing methods in LINQ but they look reasonable to me, especially assuming that they are only necessary in a less common case. I also happen to not be a fan of the TryGetX(..., out x) pattern. I know it sometimes can have advantages (especially with inline var support) but I still prefer other alternatives when available.

I do have an idea, however. Perhaps this is a bit radical, but what if we introduced an Optional type to System.Linq that we could return from these functions?

I actually think this is a very interesting idea. In fact while going through some of the proposed designs on the whiteboard a few days ago I also thought that tuples weren't nice enough and that introducing a type that had more specific semantics would probably make sense. I was actually surprised that @bartdesmet had not mentioned this possibility already, or did he? :smile:

But then I realized that an IQueryable<T> or IEnumerable<T> can convey a very similar concept and so I rejected (perhaps too quickly?) the idea of introducing a new type. E.g., the result of

    source.Where(predicate).Take(1);

already conveys mostly the same concept as an Optional<T>, with the possible advantage that it stays in the same monad that you started on, e.g. if source happens to be an IQueryable<T> the result would also be an IQueryable<T> and conversely, if source is an IEnumerable<T> the result would be an IEnumerable<T>.

However I understand that a type like the one you are proposing (or perhaps the equivalent interface) would carry more precise semantics. E.g. an instance of Optional<T> could never represent a collection of multiple elements. I also suspect the type you propose could find other applications in the future, e.g.: to represent optional services on https://github.com/aspnet/DependencyInjection/, a new general API pattern that replaces TryGetX(..., out x), and perhaps even as part of the [non]nullable reference types story?

Initially I was a bit worried about the possibility of a proliferation of types, but I am not sure these are real issues:

Also, this particular code snippet:

if (ints.TryGetFirst().TryGetValue(out i))
{
    Console.WriteLine(i);
}

made me think that perhaps we should consider coming up with a new naming pattern for methods that return Optional<T> rather than overloading the existing TryGet... naming. E.g.:

Although I am not convinced of this either (I don't like any of the names I could come up with).

I would love to hear what others think about your idea.

jamesqo commented 7 years ago

@divega I was waiting for others to reply in the meantime, but since no one has:

I also suspect the type you propose could find other applications in the future,

I think so too; places like Dictionary.TryGetValue would be ideal candidates.

Would we need a separate IQueryable flavor o Optional or would the IQueryable version of the methods just return the same types as the IEnumerable versions?

Would we eventually need an async version of Optional or will [Value]Task<Optional> and Optional<[Value]Task> be enough?

I don't think there is any need to add more "flavors"-- the type should work fine with existing containers.

I would love to hear what others think about your idea.

👍 I would also like to hear what others think.

Also: If we were to pursue this, a nicer (and shorter) name would be Maybe<T>.

divega commented 7 years ago

cc @bartdesmet and also @MadsTorgersen and @mattwar for their perspective from the original LINQ design philosophy (there have been a few recent discussions about new LINQ operators that could use your insights, I will try to tag you more often).

MadsTorgersen commented 7 years ago

Let me express an opinion here, as someone who was around for at least part of the original LINQ design. @mattwar was more deeply involved in shaping IQueryable and may have additional and/or conflicting perspective. :-)

There are probably 10x (unscientific guesstimate) more people using LINQ over IEnumerable than IQueryable. We should land on the API shape that is most convenient/elegant/idiomatic for that larger crowd, and solve any IQueryable-specific problems within the scope of IQueryable. Degrading the usage experience for everyone isn't the right outcome.

For that reason, TrySingle etc. should follow the established Try pattern, with an out parameter and a bool return value. All the good arguments to that effect above apply.

What to do about IQueryable? The problem there is that the current IQueryable machinery isn't able to deliver an expression tree of such a call where part of the result comes through an out parameter. Hence proposals in dotnet/corefx#14276 to instead return a tuple or other compound result.

I think IQueryable should have such tuple-returning overloads of TrySingle etc. Those do indeed construct expression trees of calling themselves, like all other Queryable methods do.

public static (bool success, T value) TrySingle<T>(this IQueryable<T> source) { ... }

In addition there should be an overload that shadows the out-based one on IEnumerable, and simply forwards to the tuple-based one:

public static bool TrySingle<T>(this IQueryable<T> source, out T value)
{
    bool success;
    (success, value) = TrySingle(source);
    return success;
}

This limits the damage only to the Queryable arena, rather than inflict it on Enumerable users.

The "damage" is:

Thoughts?

mattwar commented 7 years ago

@MadsTorgersen Is the out variable version of TrySingle known to not work with IQueryable?

MadsTorgersen commented 7 years ago

@mattwar I'm relying on @bartdesmet's argument to that effect over in dotnet/corefx#14276.

divega commented 7 years ago

Thanks @MadsTorgersen. From the perspective of optimizing for the most common usage and the hypothesis that IEnumerable<T> is the most common scenario, I am fine with us doing what you are proposing (which is I believe what people in the thread had already arrived to before I started chiming in :grin:).

I still believe these operators are weird in that they have even more limited composability than the existing operators that return a single element (the argument I tried to make at https://github.com/dotnet/corefx/issues/6073#issuecomment-268405835), but I can be convinced that this is not as important.

jamesqo commented 7 years ago

@MadsTorgersen :shipit: Sounds like a great idea to me. I think it makes sense to stick with existing conventions (using out) and not fall too far from the tree. Tuples do have limited readability compared to Try in this kind of scenario.

mattwar commented 7 years ago

With the addition of out var it makes TryXXX pattern API's less unappealing for general code (basically because you don't have to define the variable up front.) Yet, this is still an unappealing pattern for a LINQ type API that is not only primarily expression based, but is sequence based and is likely to have nested expressions that are executed for each item in that sequence that may themselves be needing access to this API. If you only have a TryXXX pattern then this becomes a bad choice if the only way to use it requires you to use the same variable, associated with at best the outer statement scope, for each instance of the iteration. Your query now becomes side-effecting and cannot be processed in parallel.

An API that returns Option<T>, some tuple pattern, or other like structure is better for this sequence style usage, except the language does not yet offer good means to consume these compound values in an expression context.

We did consider this during original LINQ design. This is why we ended up with SingleOrDefault style API. While not perfect, it doesn't cause side-effects while nested and doesn't require new language patterns to consume.

svick commented 7 years ago

@mattwar Are you talking about something like the following?

from x in collection
where x.AnotherCollection.TrySingle(out var y)
select y

There is a proposal to make that work: https://github.com/dotnet/roslyn/issues/15619.

mattwar commented 7 years ago

@svick that would work, but only for query expression syntax. Its an awkward translation if you had to do it by hand. Assuming the translation would work at all.

karelz commented 7 years ago

The PR dotnet/corefx#14276 seems to be abandoned. Unassigning this issue - it is "up for grabs" again, available for anyone to pick it up.

karelz commented 7 years ago

It looks like we got consensus on the design of IQueryable companion APIs. Can someone please produce the full API shape for IQueryable? -- Basically list of ALL IQueryable APIs (incl. which type they will live in - Queryable?) based on what @MadsTorgersen suggested above:

    public static (bool success, T value) TrySingle<T>(this IQueryable<T> source);
    public static bool TrySingle<T>(this IQueryable<T> source, out T value);

Once we have it, we can update top-most spec and make it available for someone to implement it.

jamesqo commented 7 years ago

@karelz One thing to consider, btw, is whether we should use camelcase or pascal case for the tuple's element names, e.g. (bool success, T value) or (bool Success, T Value). I'm not sure if there's an established convention for that yet.

jnm2 commented 7 years ago

Tuples have argument list semantics rather than structure semantics. I think the more that logic about tuple values is distributive to each individual value, the better. If you wouldn't pascal-case arguments or multiple return values (if we had those), you probably shouldn't pascal-case tuple element names.

jnm2 commented 7 years ago

Is there anything I can to to help move this forward?

karelz commented 7 years ago

See my last 2 statements above: https://github.com/dotnet/corefx/issues/6073#issuecomment-273845281 and https://github.com/dotnet/corefx/issues/6073#issuecomment-273844248

jnm2 commented 7 years ago

@karelz Too late for 2.0?

karelz commented 7 years ago

I think so -- see https://github.com/dotnet/corefx/issues/17619#issuecomment-296872132 for details. It is about minimizing risk. Rushing APIs is never a smart idea. Small mistake can cost you life-time of compat problems. It's just not worth the risk, unless the value is really HUGE. Makes sense?

jnm2 commented 7 years ago

Yup, I have no problem with that. Just making sure my time is put to good use!

karelz commented 7 years ago

Yep, appreciate it. For best use of time, I'd suggest to check: dotnet/corefx#17619 (top post).

tuespetre commented 7 years ago

I was happy to read @MadsTorgersen's comment but I have to say, @MadsTorgersen, that I am kind of taken aback by the 'IQueryable can eat the table scraps' sentiment.

I have to say this is not looking good... there is already a good way to achieve the desired result without muddying up the LINQ API, and the amount of deliberation that has gone back and forth on this issue and its associated PR(s) highlights the shakiness of it.

If the main motivation is the inability to distinguish between a default value in correlation to the count of elements within a sequence, why not use more conventional means? I understand that IEnumerable is by far the majority use case for LINQ, but I don't think I could see the methods introduced by this proposal being so commonly used as to have enough benefit over some conventional equivalents that accomplish the same end result to warrant introducing so many unprecedented constructs to the LINQ API, like out parameters, tuple returns, and 'matched-mismatched' operators between Enumerable and Queryable:

// Top-level equivalent
var trySingle = xs.Where(x => x.y).Take(2);
var tryFirst = xs.Where(x => x.y).Take(1);
var tryLast = xs.Where(x => x.y).Reverse().Take(1);
var tryElementAt = xs.Where(x => x.y).Skip(index).Take(1);

var anyOneOfThoseExamples = PickOne(trySingle, tryFirst, tryLast, tryElementAt);

var result = anyOneOfThoseExamples.ToArray();

if (result.Length == 1)
{
    // Do stuff
}
else
{
    // Fall back
}
// Subquery equivalent
var sequence = from x in xs
               let y = ys.Where(y => y.x == x).Take(2).ToArray()
               select new 
               { 
                   x, 
                   y = (success: y.Length == 1, value: y[0] ) 
               };

Just my :two: 💰 ...

shmuelie commented 7 years ago

why not use more conventional means?

LINQ is by definition NOT conventional, since in any case where LINQ is used you can do it using for/foreach and collections. LINQ generally (in my opinion at least) provides developers with:

  1. Simpler ways to do things.
  2. Easier ways to think of things (aka the code reads like what it does).

While yes TrySingle, TryFirst, TryLast, and TryElementAt, can done using existing operators they are less obvious, less performant (have a huge collection and now you have to allocate an array to just find out that it has more than one item?), and do read like a sentence.

Is the LINQ API getting unmanageable? Maybe. But that should be solved by splitting up the methods, not by limiting its usefulness.

tuespetre commented 7 years ago

LINQ is by definition NOT conventional, since in any case where LINQ is used you can do it using for/foreach and collections.

I didn't mean "conventional" as in "let's take it down to the assembly registers", I meant it as in "what LINQ already offers to accomplish this."

less performant (have a huge collection and now you have to allocate an array to just find out that it has more than one item?)

Without doing a thorough analysis, I would assume that somewhere along the line, TrySingle and kin would need to allocate at least one or two objects, if not a struct under the guise of an IEnumerator or something like that. Allocating an array is nothing to worry about without profiling; especially in the examples I provided, the array would hold at most one or two elements.

Is the LINQ API getting unmanageable? Maybe. But that should be solved by splitting up the methods, not by limiting its usefulness.

I don't understand what you mean here. I don't think the LINQ API is unmanageable... yet. I think this proposal would be a big step in that direction, though. When you say "splitting up the methods", I think about how easily TrySingle is achieved by composing Take and ToArray. When you say "limiting its usefulness", I think about how TrySingle doesn't really add any new usefulness -- it adds a sugar over something that was already possible, only this sugar might make Queryable sick and throw up on itself. 🤢

shmuelie commented 7 years ago

Without doing a thorough analysis, I would assume that somewhere along the line, TrySingle and kin would need to allocate at least one or two objects, if not a struct under the guise of an IEnumerator or something like that. Allocating an array is nothing to worry about without profiling; especially in the examples I provided, the array would hold at most one or two elements.

I was incorrect there. Yes, in your examples the array would hold one or two items at most. And yes optimization without profiling is never a good idea.

don't understand what you mean here. I don't think the LINQ API is unmanageable... yet. I think this proposal would be a big step in that direction, though. When you say "splitting up the methods", I think about how easily TrySingle is achieved by composing Take and ToArray. When you say "limiting its usefulness", I think about how TrySingle doesn't really add any new usefulness -- it adds a sugar over something that was already possible, only this sugar might make Queryable sick and throw up on itself. 🤢

I think the key word here is "sugar". Yes, TrySingle and kin are syntactic sugar, but so is all of LINQ, and even plenty of C# and VB.NET. The question (and we're going to have to agree to disagree here) is if the cost of the sugar is worth it. I think it is, and clearly you don't.

jnm2 commented 6 years ago

I don't know how to judge implementations, but I would have done this rather than foreach with a flag:

public static bool TrySingle<T>(this IEnumerable<T> source, out T value)
{
    if (source == null) throw new ArgumentNullException(nameof(source));

    using (var en = source.GetEnumerator())
    {
        if (en.MoveNext())
        {
            var temp = en.Current;

            if (!en.MoveNext())
            {
                value = temp;
                return true;
            }
        }

        value = default;
        return false;
    }
}

Just curious because stuff like this is sitting in my Extensions.cs and I may as well use the official implementation if I can deduce from the discussion what that would be.

JonHanna commented 6 years ago

@jnm2 well Single is done similarly to yours because it beat foreach with a flag when compared a while back. Also I think it's more intuitive; look for the thing you want, and then look to see if there's another.

karelz commented 6 years ago

@jnm2 I skimmed through the thread -- looks like we are still waiting on https://github.com/dotnet/corefx/issues/6073#issuecomment-273845281. Nobody picked it up yet:

Can someone please produce the full API shape for IQueryable? -- Basically list of ALL IQueryable APIs (incl. which type they will live in - Queryable?) based on what @MadsTorgersen suggested above:

public static (bool success, T value) TrySingle<T>(this IQueryable<T> source);
public static bool TrySingle<T>(this IQueryable<T> source, out T value);

Once we have it, we can update top-most spec and make it available for someone to implement it.

jnm2 commented 6 years ago

Here's what I care about, plus the IQueryable versions. Are we doing predicate overloads?

namespace System.Linq
{
    public static class Enumerable
    {
+       public static bool TrySingle<TSource>(this IEnumerable<TSource> source, out TSource element);
    }

    public static class Queryable
    {
+       public static (bool success, T value) TrySingle<T>(this IQueryable<T> source);
+       public static bool TrySingle<T>(this IQueryable<T> source, out T value);
    }
}
karelz commented 6 years ago

@jnm2 we need also the other APIs - someone should reread the thread discussion and find out if we planned to do all.

jnm2 commented 6 years ago

I reread the whole thread twice just now and don't see much mention of either the TryFirst, TryLast and TryElementAt variants or the predicate variants. Certainly not in conjunction with Queryable. I'm not sure why it's me doing this, but here's everything, just to get the ball rolling:

namespace System.Linq
{
    public static class Enumerable
    {
        public static TSource Single<TSource>(this IEnumerable<TSource> source);
        public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
        public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source);
        public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
+       public static bool TrySingle<TSource>(this IEnumerable<TSource> source, out TSource element);
+       public static bool TrySingle<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);

        public static TSource First<TSource>(this IEnumerable<TSource> source);
        public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
        public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source);
        public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
+       public static bool TryFirst<TSource>(this IEnumerable<TSource> source, out TSource element);
+       public static bool TryFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);

        public static TSource Last<TSource>(this IEnumerable<TSource> source);
        public static TSource Last<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
        public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source);
        public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
+       public static bool TryLast<TSource>(this IEnumerable<TSource> source, out TSource element);
+       public static bool TryLast<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);

        public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index);
        public static TSource ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, int index);
+       public static bool TryElementAt<TSource>(this IEnumerable<TSource> source, int index, out TSource element);
    }

    public static class Queryable
    {
        public static TSource Single<TSource>(this IQueryable<TSource> source);
        public static TSource Single<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
        public static TSource SingleOrDefault<TSource>(this IQueryable<TSource> source);
        public static TSource SingleOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
+       public static (bool success, T value) TrySingle<T>(this IQueryable<T> source);
+       public static (bool success, T value) TrySingle<T>(this IQueryable<T> source, Expression<Func<TSource, bool>> predicate);
+       public static bool TrySingle<TSource>(this IQueryable<TSource> source, out TSource element);
+       public static bool TrySingle<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, out TSource element);

        public static TSource First<TSource>(this IQueryable<TSource> source);
        public static TSource First<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
        public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source);
        public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
+       public static (bool success, T value) TryFirst<T>(this IQueryable<T> source);
+       public static (bool success, T value) TryFirst<T>(this IQueryable<T> source, Expression<Func<TSource, bool>> predicate);
+       public static bool TryFirst<TSource>(this IQueryable<TSource> source, out TSource element);
+       public static bool TryFirst<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, out TSource element);

        public static TSource Last<TSource>(this IQueryable<TSource> source);
        public static TSource Last<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
        public static TSource LastOrDefault<TSource>(this IQueryable<TSource> source);
        public static TSource LastOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
+       public static (bool success, T value) TryLast<T>(this IQueryable<T> source);
+       public static (bool success, T value) TryLast<T>(this IQueryable<T> source, Expression<Func<TSource, bool>> predicate);
+       public static bool TryLast<TSource>(this IQueryable<TSource> source, out TSource element);
+       public static bool TryLast<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, out TSource element);

        public static TSource ElementAt<TSource>(this IQueryable<TSource> source, int index);
        public static TSource ElementAtOrDefault<TSource>(this IQueryable<TSource> source, int index);
+       public static (bool success, T value) TryElementAt<T>(this IQueryable<T> source, int index);
+       public static bool TryElementAt<TSource>(this IQueryable<TSource> source, int index, out TSource element);
    }
}
karelz commented 6 years ago

@jnm2 I'm not sure why it's me doing this

You were interested in the issue and no one else was interested to follow up in the last year, which means it won't happen otherwise. I am just trying to help here to push it forward and unblock contributors (you) :) ... maybe I misunderstood your intent?

karelz commented 6 years ago

LGTM, adding it to the top post (it is considered approved - I will ping API reviewers as FYI). Thanks @jnm2! FYI: @OmarTawfik @VSadov

Next steps: it is available for implementation if someone wants to grab it (not blocked anymore).

jnm2 commented 6 years ago

I'm interested but I must be the least knowledgeable person in this thread... I guess I won't let that stop me in the future. 😂

jnm2 commented 6 years ago

I'm also interested enough to implement it if no one else does...