Reputation: 5421
I would like something like IEnumerable.FirstOrDefault
, but I would like the default value returned if length != 1
. Something like v.Count() == 1 ? v[0] : default
. But I would prefer to do it without converting the entire enumerable into an array. What is the best way to get this behavior?
Upvotes: 2
Views: 1204
Reputation: 2820
Have you tried to write a custom extension method, something like:
public static TSource MyFirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
return source.Count() == 1 ? source.First() : default(TSource);
}
but you need to keep in mind that methods Count()
and First()
will need to instantiate the inner collection of elements in order to get the count (or the first element).
Hope, that will help you.
Upvotes: 0
Reputation: 55389
Microsoft's implementation of Enumerable.FirstOrDefault works directly with the underlying enumerator. Yours can too:
public static T FirstIfOnlyOne<T>(this IEnumerable<T> source, T defaultValue = default(T))
{
// (Null check and IList<T> optimization omitted...)
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext()) // Is there at least one item?
{
T item = enumerator.Current; // Save it.
if (!enumerator.MoveNext()) // Is that it?
return item;
}
}
return defaultValue;
}
This is likely the most efficient implementation possible.
Upvotes: 9
Reputation: 117064
It is a good idea to avoid iterating the enumerable more than once as not all enumerables repeat the same values.
In order to ensure that your enumerable only returns 1 element it is the most efficient to take the first two elements from the enumerable and convert the result into an array. If the array then has two elements you know that the enumerable has more than one element without iterating the entire enumerable. It is possible for an enumerable to be infinitely long.
So here's how to do it:
public static T OneOnlyThenFirstOrDefault<T>(this IEnumerable<T> source)
{
var beginning = source.Take(2).ToArray();
return beginning.Length == 1 ? beginning[0] : default(T);
}
Upvotes: 7