Как насчет метода SingleOrNew() вместо SingleOrDefault() в LINQ?

Метод SingleOrDefault() велик, потому что он не генерирует исключение, если коллекция, с которой вы его вызываете, пуста. Однако иногда я хочу получить новый объект какого-либо типа, если ничего не существует. Например, было бы здорово, если бы я мог сделать следующее:

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

Таким образом, мне не нужно проверять, если он null, и если он создает новый, я всегда знаю, что мой объект client будет тем, что я могу использовать сразу.

Любые идеи?

Ответ 1

Действительно, вы просто хотите использовать оператор null coalescing. Пример:

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

Это вернет то, что возвращает SingleOrDefault, за исключением того, что если SingleOrDefault возвращает значение null, выражение возвращает вместо него новый Client().

Изменить: Как отметил Джон Скит, это решение не проводит различия между ситуацией, когда нет совпадения, и найден нулевой элемент, хотя это явно не обязательно является проблемой во многих случаях, Альтернативой является создание метода расширения следующим образом.

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

Я бы сказал, что это, наверное, самое элегантное решение, которое работает в общем случае.

Ответ 2

Если все, что вы хотите выполнить, - это переопределить значение по умолчанию (и вернуть новый объект), вы можете сделать это, используя DefaultIfEmpty ( ) Метод перед вызовом SingleOrDefault(). Что-то вроде:

var client = db.Clients
    .Where(c => c.Name == name)
    .DefaultIfEmpty(new Client { Name = name })
    .SingleOrDefault();

Ответ 3

Будет ли это делать? EDIT Оказывается, что??? не будет работать с общим типом.

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;
    }
}

Ответ 4

Похоже, это можно сделать, да. Не могу сказать, что я помню, что был в ситуации, когда я буду использовать ее сам, но ее достаточно просто реализовать. Что-то вроде этого:

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;
    }
}

Я добавлю его в список операторов, которые будут включены в MoreLinq...

Другим, более общим подходом было бы предоставление делегата, который был бы оценен только при необходимости (мне нужно, чтобы это имя было лучше):

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;
    }
}

Ответ 5

Почему бы вам не добавить его с помощью метода расширения? Похоже, это было бы удобно.

Ответ 6

var theClient =
    Repository<Client>
        .Entities()
        .Where(t => t.ShouldBeHere())
        .SingleOrDefault()
        ?? new Client { Name = "Howdy!" }
    ;