views:

428

answers:

3

The IEnumerable extension method FirstOrDefault didn't exactly do as I wanted so I created FirstOrValue. Is this a good way to go about this or is there a better way?

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, T value)
{
    T first = source.FirstOrDefault(predicate);
    return Equals(first, default(T)) ? value : first;
}
+4  A: 

default(T) will return null by default for reference types.

I would do this

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, T value)
{
    T first = source.FirstOrDefault(predicate);
    return first ?? value;
}
Daniel A. White
I'll just write it on a single line:return source.FirstOrDefault(predicate) ?? value;
Zote
extra points for null coalescing
Matt Briggs
@zote: doing it daniels way makes it easier to attach a debugger and see what is going on
Matt Briggs
But this won't work as ?? can only be used on nullable types.
Aurequi
Also won't work if null is a valid element of your collection.
Jacob
A: 

Seems reasonable to me if you want to tweak the readability instead of using DefaultIfEmpty.

You could also create an override that uses a lambda if the creation of the default value is expensive, creating it only if necessary.

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T> getValue)
{
    T first = source.FirstOrDefault(predicate);
    return Equals(first, default(T)) ? getValue() : first;
}
Chris Patterson
Very nice, the addition of a lambda makes it even more useful.
Aurequi
+17  A: 

Your code is probably incorrect; you probably haven't considered all of the cases.

Of course, we cannot know if any code is correct or incorrect until we have a spec. So start by writing a one-line spec:

"FirstOrValue<T> takes a sequence of T, a predicate, and a value of T, and returns either the first item in the sequence that matches the predicate if there is one, or, if there is not, the stated value."

Does your attempt actually implement that spec? Certainly not! Test it:

int x = FirstOrValue<int>( new[] { -1, 0, 1 }, y=>y*y==y, -1);

this returns -1. The correct answer according to the spec is 0. The first item that matches the predicate is zero, so it should be returned.

A correct implementation of the spec would look like:

public static T FirstOrValue<T>(this IEnumerable<T> sequence, Func<T, bool> predicate, T value)
{
    if (sequence == null) throw new ArgumentNullException("sequence");
    if (predicate == null) throw new ArgumentNullException("predicate");
    foreach(T item in sequence)
        if (predicate(item)) return item;
    return value;
}

Always write a spec first, even if it's only a single sentence.

Eric Lippert