Reputation: 5780
Quick question, what's the preferable way to programmaticaly ask "Is there exactly one element in this sequence that satisfies X condition?" using Linq?
i.e.
// Pretend that the .OneAndOnlyOne() method exists
int[] sequence = new int[] { 1, 1, 2, 3, 5, 8 };
Assert.IsTrue(sequence.OneAndOnlyOne(x => x == 2);
Assert.IsFalse(sequence.OneAndOnlyOne(x => x == 1);
something like this can be done with:
sequence.SingleOrDefault(x => x == 2) != null;
but that's a little clunky.
I suppose I could roll my own extension method, but this seems to be a common pattern in my code and I want to make sure there's a good clean way to do it. Is there a way using the built-in LINQ methods?
Upvotes: 32
Views: 9988
Reputation: 161
Once I had the issue to determine if exactly one item is contained I decided to create following extension. In my optionion it fits good into the set of already existing extensions (e.g.: SingleOrDefualt, FirstOrDefault ...).
public static class IEnumerableExtension
{
public static TProject OneOrDefault<TInput, TProject>(this IEnumerable<TInput> sequence, Func<TInput, TProject> projection)
{
var enumerator = sequence.GetEnumerator();
bool firstMoveNext = enumerator.MoveNext();
TInput current = enumerator.Current;
if (firstMoveNext && !enumerator.MoveNext())
{
return projection(current);
}
else
{
return default;
}
}
}
Upvotes: 1
Reputation: 18296
You could use this extension method, which I think should have been included in the standard extension methods for linq:
public static int CountUpTo<T>(this IEnumerable<T> sequence, int maxCount) {
if (sequence == null) throw new ArgumentNullException("sequence");
if (maxCount < 0) throw new ArgumentOutOfRangeException("maxCount");
var count = 0;
var enumerator = sequence.GetEnumerator();
while (count < maxCount && enumerator.MoveNext())
count += 1;
return count;
}
Used like so:
return sequence.CountUpTo(2) == 1;
Upvotes: 2
Reputation: 144196
You could do:
bool onlyOne = source.Where(/*condition*/).Take(2).Count() == 1
Which will prevent count from enumerating a large sequence unnecessarily in the event of multiple matches.
Upvotes: 55
Reputation: 55009
The simplest way is to just use Count. Single won't work for you, because it throws an exception if there isn't just that single element.
LBushkin suggests (in the comments) to use SequenceEqual to compare a sequence with another one. You could use that by skipping the first element with Skip(1), and comparing the resulting sequence to an empty sequence such as what you can get from Empty
Upvotes: 7