Open shmuelie opened 8 years ago
If one was going to do this, I would see it as cleaner if all of the methods with XxxOrDefault()
had a TryXXX
.
I do see what you're getting at but I'm not "replacing" the SingleOrDefault()
method, since that still throws an exception.
I'd say any API additions with the Try
pattern should be held off until the Tuple design for C# 7 is finalized. It might be better for these types of methods to return tuples.
I actually like the way this works because it makes using this method in a if
statement very easy:
int I;
if (seq.TrySingle(out I))
{
...
}
(I admit though that TryXXX
might not be the best name for this method though)
I wouldn't suggest any of them as a replacement (I'd argue vehemently against), but rather just as one might say "I want to get the single matching element here, or if there isn't a single matching element, know that I failed to get it" one might also say "I want to get the first matching element here, or if there isn't one, know that I failed to get it" and so on.
FWIW, some code I'm playing with now has a TryGetFirst
, TryGetLast
and TryGetElementAt
because it became necessary to distinguish from default(TSource)
of a failure and of the element happening to be the default.
Ah yes, for Last
, First
, and ElementAt
a TryXXX
method would be great. The issue here is that I'm trying to avoid an exception which event the xxxOrDefault
doesn't do. My use of the TryXXX
naming may not have been the best choice.
Yep. Much of the time the SingleOrDefault
would work, but if the default value is a possible real value, then it's no good to you.
How would SingleOrDefault
work? My case is that having more than one item is common and that means both Single
and SingleOrDefault
throw an exception, something I really don't want happening! (SingleOrDefault
returns default on no items)
Ah. I get your meaning now. Single of course has two failure modes...
Maybe a name other than TrySingle
?
I wonder if a caller might want to distinguish failure because there are none from failure because there are more than one?
So say return an enum?
enum SequenceLength
{
Empty,
Single,
More
}
After the discussion above, some more thinking myself, and looking at the contributing guidelines...
In LINQ we have the Single
and SingleOrDefault
methods:
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);
}
}
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.
I propose the following methods be added:
namespace System.Linq
{
public static class Enumerable
{
public static bool Single<TSource>(this IEnumerable<TSource> source, out TSource element);
public static bool Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);
}
}
public static bool Single<TSource>(this IEnumerable<TSource> source, out TSource element);
1) If source
is null
, throw System.ArgumentNullException
.
2) If source
is empty or has more than one element, set element
to default(TSource)
and return false
.
3) If source
has one element, set element
to that element and return true
.
public static bool Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource element);
1) If source
or predicate
is null
, throw System.ArgumentNullException
.
2) If source
has no elements or has no elements that predicate
returns true
for or has more than one element that predicate
returns true
for, set element
to default(TSource)
and return false
.
3) If source
has one element that predicate
returns true
for, set element
to that element and return true
.
As to @JonHanna 's point about wanting to know more information, I feel that would be a whole new method.
...if more than one element is common/expected, then you have one of two situations:
First(...)
would be appropriate.Single(...)
is appropriate."Exceptions are slow" is irrelevant in the case of requesting user input to resolve the issue - displaying to the screen and acting on the input is going to take far longer.
If you're planning on doing something like showing a pop-up with multiple results (if they exist) for selection, you're going to have to get them regardless. At that point, it'd be better to start with the results of a Where(...)
query and go from there.
What was the situation you were planning on using this method to solve?
My use case is a user is changing selection. They can:
Hmm, wouldn't that be approached as:
I mean, in the case where the user has multiple items selected and attempts your processing, you can't actually do nothing; you need to at least let them know you can't continue.
For doing things like greying out UI buttons based on number of selected items, you probably want to be using Count(...)
.
The processing is automatic and there are multiple "handlers" for selection. Some handle more than one item, this one doesn't. So I do silently do nothing. I could use Count
but I don't care beyond 2, so it would be wasting cycles counting every item
@VSadov says this is useful addition.
Without reading it all, we think TrySingle
is better name. Updating proposal on the top.
Should I create a PR based on my implementation?
Not yet, we need to do API review (scheduled every Tuesday at 10am PT). We will likely require all Try* APIs for completness (final decision hopefully tomorrow).
We'll mark it up for grabs and assign it to you @SamuelEnglard when it is ready for implementation.
Makes sense. And easy enough to do.
@karelz updated the proposal.
I prefer to solve this with T? SingleOrNull<T>(this IEnumerable<T> source) where T : struct
.
I have SingleOrNull, FirstOrNull, MaxOrNull, SumOrNull, IReadOnlyDictionary.GetValueOrNull, etc.
They are all shorthand for .AsNullable().SingleOrDefault()
, etc.
This itself is shorthand for .Select(_ => (T?)_).SingleOrDefault()
.
These are all for value types. For ref types, *OrNull
becomes *OrDefault
.
@jnm2 are you proposing changes to the API shape? Or just mentioning an alternative in general?
Personally I think the Try pattern is already well established in BCL and easier to use / comprehend.
Also, `OrDeaultdoes not work well with cases when
null` is acceptable value.
@karelz Just making sure the concept gets discussed before the API is finalized. As you pointed out, it doesn't help in cases where null
is acceptable. On that basis I don't think the API shape should be changed.
Approved as is in the top most post.
@karelz I just got cc'd from @AlexRadch's PR at https://github.com/dotnet/corefx/pull/14276. I know this is the second time I've done this today, but can the naming of this API be reconsidered?
I wholly understand and agree with adding this API. However, as a contributor to Linq, I feel rather strongly that TryGetSingle
would be a better name. Virtually all of the Try*
methods-- TryGetValue
, TryAdd
, etc-- have a verb after the Try
. This translates in my mind as "try getting this value/adding this item, and return whether we succeeded." TrySingle
is a verb + a noun, which does not translate smoothly in my mind at all. Same for TryFirst
, TryLast
, and TryElementAt
. I strongly suggest that the naming be reconsidered, even if it is 3 extra characters. @SamuelEnglard and others, do you disagree?
I agree on Add
. The ideal would be if there was an appropriate adjective to match most of the similar methods, but there isn't. The To…
form isn't appropriate, so really TryGet…
seems the best option here.
@karelz I can rename these methods to TryGet
. Is it approved?
These also make sense to include on Queryable
.
I do agree with @jamesqo point, though I think the missing "Get" is really with the original methods to start with.
Well, the descriptive quality of the adjectives makes sense on their own, but when the verb Try
is introduced they become stranger. Or at least now that @jamesqo pointed it out they seem strange!
On the Queryable
matter, we're keeping the parity with SkipLast
and TakeLast
and perhaps (still being discussed) restoring it for Prepend
and Append
. I think it definitely makes sense here too.
As I can see Queryable
does not have any methods with out
parameter. Is it possible to have such methods in Queryable
?
We already discussed TryGetFirst
vs. TryFirst
naming - see https://github.com/dotnet/corefx/issues/6073#issuecomment-263421263. I think of it more as pattern of adding Try
prefix before the method name.
Queryable
companion methods sound ok to me - @OmarTawfik can you please confirm? (@VSadov is OOF for another week I think).
@SamuelEnglard I have to apologize - I didn't follow up on my promise of assigning the issue to you (https://github.com/dotnet/corefx/issues/6073#issuecomment-263437700). It was by accident.
@AlexRadch that's another reason why it would be great if you can please ping each issue before you grab it in future. It will be easier to organize who works on what - avoiding duplicated efforts, "stolen features", etc. Thank you!
@karelz sorry. I did not see this comment.
That makes two of us ;-) -- I think it is as a good motivation to ping the issue in future, before you start working on it :)
@karelz it's cool. I've literally have the code sitting on my machine, just haven't had the time to "move" it to my fork to push up so no time lost on my end
We already discussed TryGetFirst vs. TryFirst naming - see dotnet/corefx#6073 (comment). I think of it more as pattern of adding Try prefix before the method name.
We didn't really discuss it as much as close over it.
We didn't really discuss it as much as close over it.
:+1:
@karelz, to give you a better sense of how I am thinking about this, it is like in English saying "try train" instead of "try catch train" or "try car" instead of "try stop car". I think it would make much more sense with a verb.
Grammatically, I agree that TryGetSingle
is best. With TrySingle
, You're not "trying to single" (single as a verb can only be used transitively) and you're not "trying a single" (the trying is being applied to the sequence, not to the result).
You can sacrifice grammatical sense for terseness, but in this case it just sounds awkward.
Also we have TryGetValue
on dictionaries. We should stick with existing standards.
Using current spec but WIP https://github.com/SamuelEnglard/corefx/blob/TrySingle-Issue6073/src/System.Linq/src/System/Linq/Single.cs
We didn't really discuss it as much as close over it.
What I meant we discussed it briefly in triage with @VSadov and @OmarTawfik. It was raised here (https://github.com/dotnet/corefx/issues/6073#issuecomment-183438071) by @JonHanna, but it didn't spark deeper discussion here. Moreover, in the API review (with English native speakers), no one raised it either.
I personally believe that having the name closer to Single
method name (BTW: was it a mistake of the past to name it GetSingle
in the first place?) is better for discoverability of the API and to create connection between the 2 APIs.
But to be honest, I don't care too much what we choose + I am ESL, so I wouldn't push too hard for my opinion anyway ;-).
@terrajobst @danmosemsft @bartonjs @KrzysztofCwalina @stephentoub @weshaggard can you please vote on the name choice TrySingle
vs. TryGetSingle
? Do we have guidelines? Do we care? (Proper English grammar TryGetSingle
vs. using Try
as prefix to existing method names - TrySingle
)
can you please vote on the name choice TrySingle vs. TryGetSingle?
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.
I concur with Steve. The original mistake was having a method with no verb, but we should stick to the naming scheme of just prepending "Try". Because the breakdown is "try that other method... return false if it would have thrown an exception had I called it". Aka
try { ret = Single(); return true; } catch { return false; }
(And, yeah, Karel, I'm agreeing with you, too, but you mentioned both sides and asked a question, Steve only answered :smile:)
@stephentoub @bartonjs I generally think of Linq's extension methods as properties rather than methods. First()
in my mind is not performing an action, but retrieving a trivially obtainable aspect of the enumerable. However, with Try
the operation is no longer trivial because it can potentially fail. So my mind converts Single
(an aspect) -> GetSingle()
(an action) -> TryGetSingle()
(an action that might fail).
Having said that, I do not want to block this proposal since there is already a PR for it. Plus @bartonjs' comment clarifies things a bit more. I guess having TryX
would be OK.
@jamesqo all the methods we're adding TryX
to had the potential to fail in the first place.
@karelz Can this be API reviewed again tomorrow for the corresponding methods to be added to Queryable
? All of the TryFirst
, TryLast
, TryElementAt
, and TrySingle
overloads mentioned in the description?
@jamesqo There is no solution how to add TryX methods to Queryable.
@AlexRadch I only glanced at his comments, but I think @bartdesmet mentioned some possible solutions at https://github.com/dotnet/corefx/pull/14276#discussion_r91574861? It seems like just he's waiting for input from @divega about which is the best method.
Motivation
In LINQ we have the
Single
andSingleOrDefault
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 ofpredicate
) they all throw aSystem.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
Original
I'd like to propose a new LINQ method:
TrySingle
. The idea is pretty much the same as the existingSingle
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:bool
.true
if the sequence contains exactly one item,false
otherwise.TSource
. This will "hold" the single element on success. On failure it is filled withdefault(TSource)
.I have found this method very useful in my own code and if accepted can quickly put together a pull request.