tags:

views:

1688

answers:

6

The SingleOrDefault() method is great because it doesn't throw an exception if the collection you're calling it against is empty. However, sometimes what I want is to get a new object of some type if nothing exists. For example it would be great if I could do the following:

var client = db.Clients
    .Where(c => c.Name == "Some Client")
    .SingleOrNew<Client>();

This way I don't have to check if it's null and if it is create a new one, I always know that my client object will be something I can use right away.

Any ideas?

+1  A: 

Why don't you add it via an Extension method then? Sounds like it would be handy.

Paul Betts
+2  A: 

Sounds like it could be done, yes. Can't say I remember having been in a situation where I'd use it myself, but it's easy enough to implement. Something like this:

public static T SingleOrNew<T>(this IEnumerable<T> source) where T : new()
{
    if (source == null)
    {
        throw new ArgumentNullException("source"); 
    }
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
        {
            return new T();
        }
        T first = iterator.Current;
        if (iterator.MoveNext())
        {
            throw new InvalidOperationException();
        }
        return first;
    }
}

I'll add it to the list of operators to include in MoreLinq...

Another, more general approach would be to provide a delegate which would only be evaluated if necessary (I need a better name here, admittedly):

public static T SingleOrSpecifiedDefault<T>(this IEnumerable<T> source,
     Func<T> defaultSelector)
{
    if (source == null)
    {
        throw new ArgumentNullException("source"); 
    }
    if (defaultSelector == null)
    {
        throw new ArgumentNullException("defaultSelector"); 
    }
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
        {
            return defaultSelector();
        }
        T first = iterator.Current;
        if (iterator.MoveNext())
        {
            throw new InvalidOperationException();
        }
        return first;
    }
}
Jon Skeet
SingleOrValue would seem a better name, or maybe SingleOrFallback.
Jeff Yates
I like "SingleOrFallback" - great name!
Jon Skeet
+11  A: 

Really, you just want to use the null coalescing operator here. Example:

var client = db.Clients
    .Where(c => c.Name == "Some Client")
    .SingleOrDefault() ?? new Client();

This will return whatever SingleOrDefault returns, except that if SingleOrDefault returns null, the expression returns new Client() instead.

Edit: As John Skeet pointed out, this solution doesn't differentiate between the situation where there is no match and a null element is found, though clearly this is not necessarily a problem in many cases. An alternative is to create an extension method as follows.

public static T SingleOrNew<T>(this IEnumerable<T> query) where T : new()
{
    try
    {
        return query.Single();
    }
    catch (InvalidOperationException)
    {
        return new T();
    }
}

I would say this is probably the most elegant solution that works in the general case.

Noldorin
The problem with that (IMO) is that it hides the case where the resulting sequence contains exactly one entry which *is* null. I think it's good to be able to distinguish between "empty" and "sequence containing null".
Jon Skeet
I've updated my post to provide an alternative solution, which gets around this problem (though honestly, I feel this solution is fine, as long as you know there are no null elements, which is very often the case).
Noldorin
why would a sequence of Clients contain a null client? for all I know, that may exist, but in my head that just doesn't make sense... from a db, that would kind of mean a null row? which to me would mean a non-existing row? which means it wouldn't be there in the first place? or?
Svish
You're right, in a database you should never get null rows (as I understand)... as in very many other situations. Still, it's worth considering at least hypothetically. The OP can use whichever solution is most appropiate in their case.
Noldorin
I agree with Svish, it's not relevant here, or in 99.99% of situations. For example, the 'Where' clause would fail in trying to access 'c.Name' if the element were null... Almost any sequence, if it is ordered, filtered, or projected in any way, could not have a 'null' within it for this reason.
Mike Rosenblum
+4  A: 

Would this do? EDIT Turns out that ?? won't work with the generic type.

public static class IEnumerableExtensions
{
    public static T SingleOrNew<T>( this IEnumerable<T> query ) where T : new()
    {
        var value = query.SingleOrDefault();
        if (value == null)
        {
            value = new T();
        }
        return value;
    }
}
tvanfosson
+5  A: 

If all you want to accomplish is to override the default value (and return a new object), you may be able to do so by using the DefaultIfEmpty() Method before calling SingleOrDefault(). Something like:

var client = db.Clients
    .Where(c => c.Name == name)
    .DefaultIfEmpty(new Client { Name = name })
    .SingleOrDefault();
Eric King
A: 
var theClient =
    Repository<Client>
        .Entities()
        .Where(t => t.ShouldBeHere())
        .SingleOrDefault()
        ?? new Client { Name = "Howdy!" }
    ;
Justice