FirstOrDefault: значення за замовчуванням, відмінне від null


142

Як я розумію, метод Linq FirstOrDefault()може повернути Defaultзначення чогось іншого, ніж null. Те, що я не працював, це те, що інші речі, крім null, можуть бути повернуті цим (і подібним) методом, коли в результаті запиту немає елементів. Чи існує якийсь конкретний спосіб, який можна налаштувати так, що якщо немає значення для конкретного запиту, якесь заздалегідь задане значення повертається як значення за замовчуванням?


147
Замість цього YourCollection.FirstOrDefault()можна використовувати, YourCollection.DefaultIfEmpty(YourDefault).First()наприклад.
лень

6
Я шукав щось подібне до вищевказаного коментаря досить довгий час, це надзвичайно допомогло. Це має бути прийнятою відповіддю.
Брендон

Наведений коментар - найкраща відповідь.
Том Паділья

У моєму випадку відповідь @sloth не спрацювала, коли повернене значення є нульовим і присвоюється ненульовому. Я MyCollection.Last().GetValueOrDefault(0)для цього використовував . Інакше відповідь @Jon Skeet нижче - IMO правильна.
Jnr

Відповіді:


46

Загальний випадок, а не лише для типів значень:

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

199

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

Ні. А точніше, воно завжди повертає значення за замовчуванням для типу елемента ..., що є або нульовою посиланням, нульовим значенням типу нульового значення, або природним значенням "всі нулі" для типу, що не зводиться до нуля.

Чи існує якийсь конкретний спосіб, який можна налаштувати так, що якщо немає значення для конкретного запиту, якесь заздалегідь задане значення повертається як значення за замовчуванням?

Для довідкових типів ви можете просто використовувати:

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

Звичайно, це також дасть вам "інше значення за замовчуванням", якщо перше значення присутнє, але це нульове посилання ...


Я знаю, що питання задає тип посилання, але ваше рішення не працює, коли елементи типу значень int. Я вважаю за краще використовувати DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Працює як для типу значення, так і для еталонного типу.
KFL

@KFL: Для ненульових типів значень я б, мабуть, також використовував це, але він є більш довгомірним для змінних справ.
Джон Скіт

Чудовий контроль над типами повернення, коли за замовчуванням є нульовим .. :)
Sundara Prabu,

"Ні. А точніше, воно завжди повертає значення за замовчуванням для типу елемента ..." - Це зробило це для мене, власне .., оскільки я також неправильно зрозумів значення імені функції, припускаючи, що ви можете надати будь-яке значення за замовчуванням, коли потрібно
Ісус Кампон

Мені дуже сподобався такий підхід, але я змінив "T alternate" на "Func <T> alternate", а потім "return alternate ();" таким чином я не генерую зайвий об'єкт, якщо мені не потрібно. Особливо корисно, якщо функція викликається багато разів поспіль, конструктор повільний або альтернативний екземпляр типу займає багато пам'яті.
Dan Violet Sagmiller

64

Ви можете використовувати DefaultIfEmpty, а потім Перший :

T customDefault = ...;
IEnumerable<T> mySequence = ...;
mySequence.DefaultIfEmpty(customDefault).First();

Мені подобається ідея DefaultIfEmpty- вона працює з ВСІми API, для яких потрібно вказати за замовчуванням:, First()і Last()т. Д. Як користувач, вам не потрібно пам’ятати, які API дозволяють визначати типові, які не. Дуже елегантно!
KFL

Це дуже відповідь на гніздо.
Джессі Вільямс

19

З документації для FirstOrDefault

[Повертає] за замовчуванням (TSource), якщо джерело порожнє;

З документації за замовчуванням (T) :

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

Тому значення за замовчуванням може бути нульовим або 0, залежно від того, тип є еталонним чи типовим типом, але ви не можете контролювати поведінку за замовчуванням.


6

Скопійовано з коментаря @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 елементів). Якщо ви використовуєте FirstЗ відповідним виразом, як у вашому прикладі, і в цій умові не знайдено жодного елемента, повернене значення буде порожнім.
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!


2
Єдина відмінність цієї відповіді від відповіді вітаміну С полягає в тому, що цей використовується FirstOrDefaultзамість First. Згідно з повідомленням msdn.microsoft.com/en-us/library/bb340482.aspx , рекомендоване використанняFirst
Daniel

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 або новішої версії:

Використовуйте ?.або ?[перевіряйте, чи немає, перш ніж виконати доступ до документації щодо умовно-умовних операторів

Приклад: var barCSharp6 = list.FirstOrDefault()?.Bar;

C # старіша версія:

Використовуйте DefaultIfEmpty()для отримання значення за замовчуванням, якщо послідовність порожня. Документація MSDN

Приклад: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;


1
Оператор поширення нуля не дозволений у ламбах дерева виразів.
Lars335

1

Замість цього YourCollection.FirstOrDefault()можна використовувати, YourCollection.DefaultIfEmpty(YourDefault).First()наприклад.


Коли ви думаєте, що це було корисно, ви можете висловити позицію. Це не відповідь.
jannagy02

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())

Тож для мене я просто хотів використовувати вбудований резолютор за замовчуванням, я можу зробити звичайну перевірку, а потім передати функцію, щоб клас не був ініційованим, навіть якщо він не використовується, його функція виконувати, коли вимагається замість цього!


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.