FirstOrDefault: значение по умолчанию, отличное от нуля

146

Насколько я понимаю, в Linq метод FirstOrDefault()может возвращать Defaultзначение, отличное от null. Что я не понял, так это то, какие вещи, кроме null, могут быть возвращены этим (и подобным) методом, когда в результате запроса нет элементов. Есть ли какой-то особый способ настроить это так, чтобы при отсутствии значения для определенного запроса какое-то предопределенное значение возвращалось как значение по умолчанию?

Сачин Каинт
источник
150
Вместо YourCollection.FirstOrDefault(), YourCollection.DefaultIfEmpty(YourDefault).First()например , вы можете использовать .
ленивец
7
Я давно искал что-то вроде приведенного выше комментария, это очень помогло. Это должен быть принятый ответ.
Brandon
Приведенный выше комментарий - лучший ответ.
Том Падилла
В моем случае ответ @sloth не сработал, если возвращаемое значение имеет значение NULL и присвоено значение, не допускающее значения NULL. Я использовал MyCollection.Last().GetValueOrDefault(0)для этого. В противном случае ответ @Jon Skeet ниже является правильным ИМО.
Jnr

Ответы:

48

Общий случай, не только для типов значений:

static class ExtensionsThatWillAppearOnEverything
{
    public static T IfDefaultGiveMe<T>(this T value, T alternate)
    {
        if (value.Equals(default(T))) return alternate;
        return value;
    }
}

var result = query.FirstOrDefault().IfDefaultGiveMe(otherDefaultValue);

Опять же , это не может точно сказать , если было что - нибудь в вашей последовательности, или , если первое значение было по умолчанию.

Если вам это небезразлично, вы можете сделать что-нибудь вроде

static class ExtensionsThatWillAppearOnIEnumerables
{
    public static T FirstOr<T>(this IEnumerable<T> source, T alternate)
    {
        foreach(T t in source)
            return t;
        return alternate;
    }
}

и использовать как

var result = query.FirstOr(otherDefaultValue);

хотя, как указывает г-н Стейк, с тем же успехом это может сделать .DefaultIfEmpty(...).First().

Роулинг
источник
Ваши общие методы нуждаются <T>в своих именах, но более серьезно то, что value == default(T)это не работает (потому что, кто знает, Tможно ли сравнивать их для равенства?)
AakashM
Спасибо, что указали на это, @AakashM; Я действительно пробовал это сейчас, и я думаю, что все должно быть в порядке (хотя мне не нравится бокс для типов значений).
Роулинг,
3
@Rawling Используйте, EqualityComparer<T>.Default.Equals(value, default(T))чтобы избежать бокса и избежать исключения, если значение равноnull
Lukazoid
204

Насколько я понимаю, в Linq метод FirstOrDefault () может возвращать значение по умолчанию, отличное от нуля.

Нет. Или, скорее, он всегда возвращает значение по умолчанию для типа элемента ... которое является либо нулевой ссылкой, нулевым значением типа значения, допускающим значение NULL, либо естественным значением «все нули» для типа значения, не допускающего значения NULL.

Есть ли какой-то особый способ настроить это так, чтобы, если для конкретного запроса нет значения, какое-то предопределенное значение возвращалось как значение по умолчанию?

Для ссылочных типов вы можете просто использовать:

var result = query.FirstOrDefault() ?? otherDefaultValue;

Конечно, это также даст вам «другое значение по умолчанию», если присутствует первое значение, но это пустая ссылка ...

Джон Скит
источник
Я знаю, что вопрос требует ссылочного типа, но ваше решение не работает, когда элементы являются типами значений, например int. Я предпочитаю использовать DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Работает как для типа значения, так и для ссылочного типа.
KFL
@KFL: для типов значений, не допускающих значения NULL, я бы, вероятно, тоже использовал это, но он более длинный для случаев, допускающих значение NULL.
Джон Скит
Потрясающий контроль над возвращаемыми типами, когда значение по умолчанию равно null .. :)
Сундара Прабу
"Нет. Или, скорее, он всегда возвращает значение по умолчанию для типа элемента ..." - На самом деле это помогло мне ... поскольку я также неправильно понял значение имени функции, предполагая, что вы можете указать любое значение по умолчанию, когда это необходимо
Хесус Кампон,
Мне очень понравился этот подход, но я заменил «T alternate» на «Func <T> alternate», а затем «return alternate ();» таким образом я не создаю лишний объект, если мне не нужно. Это особенно полезно, если функция вызывается много раз подряд, конструктор работает медленно или альтернативный экземпляр типа занимает много памяти.
Дэн Вайолет Сагмиллер
68

Вы можете использовать DefaultIfEmpty, за которым следует First :

T customDefault = ...;
IEnumerable<T> mySequence = ...;
mySequence.DefaultIfEmpty(customDefault).First();
Витамин С
источник
Я люблю идею DefaultIfEmpty- она работает со всеми API , которые должны по умолчанию должны быть указаны: First(), Last()и т.д. Как пользователь, вам не нужно запоминать , какие интерфейсы позволяют указать значение по умолчанию , которые не делают. Очень элегантный!
KFL
Это очень ответственный ответ.
Джесси Уильямс
19

Из документации для FirstOrDefault

[Возвращает] по умолчанию (TSource), если источник пуст;

Из документации по умолчанию (T) :

ключевое слово по умолчанию, которое вернет ноль для ссылочных типов и ноль для типов с числовыми значениями. Для структур он вернет каждый член структуры, инициализированный нулем или нулем, в зависимости от того, являются ли они типами значений или ссылочными типами. Для типов значений, допускающих значение NULL, по умолчанию возвращается System.Nullable, который инициализируется, как любая структура.

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

РБ.
источник
7

Скопировано из комментария @sloth

Вместо YourCollection.FirstOrDefault(), YourCollection.DefaultIfEmpty(YourDefault).First()например , вы можете использовать .

Пример:

var viewModel = new CustomerDetailsViewModel
    {
            MainResidenceAddressSection = (MainResidenceAddressSection)addresses.DefaultIfEmpty(new MainResidenceAddressSection()).FirstOrDefault( o => o is MainResidenceAddressSection),
            RiskAddressSection = addresses.DefaultIfEmpty(new RiskAddressSection()).FirstOrDefault(o => !(o is MainResidenceAddressSection)),
    };
Матас Вайткявичюс
источник
2
Обратите внимание, что DefaultIfEmptyвозвращает значение по умолчанию, ЕСЛИ коллекция пуста (имеет 0 элементов). Если вы используете FirstWITH совпадающее выражение, как в вашем примере, и это условие не находит ни одного элемента, возвращаемое значение будет пустым.
OriolBG
5

Вы также можете сделать это

    Band[] objects = { new Band { Name = "Iron Maiden" } };
    first = objects.Where(o => o.Name == "Slayer")
        .DefaultIfEmpty(new Band { Name = "Black Sabbath" })
        .FirstOrDefault();   // returns "Black Sabbath" 

Здесь используется только linq - yipee!

BurnWithLife
источник
2
Единственное различие между этим ответом и ответом на витамин С состоит в том, что в нем используется FirstOrDefaultвместо First. Согласно msdn.microsoft.com/en-us/library/bb340482.aspx , рекомендуемое использованиеFirst
Дэниел
5

На самом деле, NullReferenceExceptionкогда я работаю с коллекциями , я стараюсь избегать двух подходов :

public class Foo
{
    public string Bar{get; set;}
}
void Main()
{
    var list = new List<Foo>();
    //before C# 6.0
    string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;
    //C# 6.0 or later
    var barCSharp6 = list.FirstOrDefault()?.Bar;
}

Для C # 6.0 или новее:

Используйте ?.или, ?[чтобы проверить, является ли значение null перед выполнением доступа к члену документация по операторам с условием NULL

Пример: var barCSharp6 = list.FirstOrDefault()?.Bar;

C # более старая версия:

Используйте DefaultIfEmpty()для получения значения по умолчанию, если последовательность пуста. Документация MSDN

Пример: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;

Самуэль Диого
источник
1
Оператор нулевого распространения не допускается в лямбах дерева выражений.
Lars335
2

Вместо YourCollection.FirstOrDefault(), YourCollection.DefaultIfEmpty(YourDefault).First()например , вы можете использовать .

Раджа
источник
Если вы думаете, что это было полезно, вы можете проголосовать за. Это не ответ.
jannagy02 06
1

У меня была аналогичная ситуация, и я искал решение, которое позволяет мне возвращать альтернативное значение по умолчанию, не заботясь об этом на стороне вызывающего абонента каждый раз, когда мне это нужно. Что мы обычно делаем, если Linq не поддерживает то, что мы хотим, - это писать новое расширение, которое позаботится об этом. Вот что я сделал. Вот что я придумал (хотя и не проверял):

public static class EnumerableExtensions
{
    public static T FirstOrDefault<T>(this IEnumerable<T> items, T defaultValue)
    {
        foreach (var item in items)
        {
            return item;
        }
        return defaultValue;
    }

    public static T FirstOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate, T defaultValue)
    {
        return items.Where(predicate).FirstOrDefault(defaultValue);
    }

    public static T LastOrDefault<T>(this IEnumerable<T> items, T defaultValue)
    {
        return items.Reverse().FirstOrDefault(defaultValue);
    }

    public static T LastOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate, T defaultValue)
    {
        return items.Where(predicate).LastOrDefault(defaultValue);
    }
}
Харри
источник
0

Я знаю, что это было давно, но я добавлю к этому, основываясь на самом популярном ответе, но с небольшим расширением, я хотел бы поделиться следующим:

static class ExtensionsThatWillAppearOnIEnumerables
{
    public static T FirstOr<T>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T> alternate)
    {
        var thing = source.FirstOrDefault(predicate);
        if (thing != null)
            return thing;
        return alternate();
    }
}

Это позволяет мне называть его встроенным в моем собственном примере, с которым у меня возникли проблемы:

_controlDataResolvers.FirstOr(x => x.AppliesTo(item.Key), () => newDefaultResolver()).GetDataAsync(conn, item.ToList())

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

Аарон Гибсон
источник
-2

Используйте DefaultIfEmpty()вместо FirstOrDefault().

Абхишек Сингх
источник