FirstOrDefault: wartość domyślna inna niż null

142

Jak rozumiem, w Linq metoda FirstOrDefault()może zwrócić Defaultwartość czegoś innego niż null. Nie udało mi się ustalić, jakie rzeczy inne niż null mogą być zwracane przez tę (i podobną) metodę, gdy w wyniku zapytania nie ma żadnych elementów. Czy istnieje jakiś szczególny sposób, w jaki można to skonfigurować, aby w przypadku braku wartości dla określonego zapytania zwracana była wstępnie zdefiniowana wartość jako wartość domyślna?

Sachin Kainth
źródło
147
Zamiast tego YourCollection.FirstOrDefault()możesz użyć YourCollection.DefaultIfEmpty(YourDefault).First()na przykład.
lenistwo
6
Od dłuższego czasu szukałem czegoś takiego jak powyższy komentarz, bardzo pomogło. To powinna być akceptowana odpowiedź.
Brandon,
Powyższy komentarz jest najlepszą odpowiedzią.
Tom Padilla
W moim przypadku odpowiedź @sloth nie zadziałała, gdy zwrócona wartość ma wartość null i jest przypisana do wartości innej niż nullable. Używałem MyCollection.Last().GetValueOrDefault(0)do tego. W przeciwnym razie odpowiedź @Jon Skeet poniżej jest poprawna IMO.
Jnr

Odpowiedzi:

46

Przypadek ogólny, nie tylko dla typów wartości:

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

Ponownie, to naprawdę nie może powiedzieć, czy coś było w twojej sekwencji, ani czy pierwsza wartość była domyślna.

Jeśli ci na tym zależy, możesz zrobić coś takiego

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

i użyj jako

var result = query.FirstOr(otherDefaultValue);

chociaż, jak wskazuje Mr. Steak, równie dobrze można by to zrobić .DefaultIfEmpty(...).First().

Rawling
źródło
Wasze ogólne metody muszą się znajdować <T>w ich nazwach, ale poważniejsze jest to, że value == default(T)nie działa (bo kto wie, czy Tmożna porównać do równości?)
AakashM,
Dzięki za zwrócenie uwagi, @AakashM; Właściwie próbowałem tego teraz i myślę, że powinno być OK (chociaż nie lubię boksu dla typów wartości).
Rawling
3
@Rawling Użyj, EqualityComparer<T>.Default.Equals(value, default(T))aby uniknąć boksowania i uniknąć wyjątku, jeśli wartość wynosinull
Lukazoid
199

Jak rozumiem, w Linq metoda FirstOrDefault () może zwrócić domyślną wartość czegoś innego niż null.

Nie. Lub raczej zawsze zwraca wartość domyślną dla typu elementu ..., która jest albo odwołaniem o wartości null, wartością zerową typu wartości dopuszczającej wartość null, albo naturalną wartością „wszystkie zera” dla typu wartości nieprzekraczającego wartości null.

Czy istnieje jakiś szczególny sposób, w jaki można to skonfigurować, aby w przypadku braku wartości dla określonego zapytania zwracana była wstępnie zdefiniowana wartość jako wartość domyślna?

W przypadku typów referencyjnych możesz po prostu użyć:

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

Oczywiście będzie to również daje „drugą wartość domyślną”, jeśli pierwsza wartość jest obecny, ale jest odniesienie zerowa ...

Jon Skeet
źródło
Wiem, że pytanie dotyczy typu referencyjnego, ale twoje rozwiązanie nie działa, gdy elementy są typami wartości int. Wolę użycia DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Działa zarówno dla typu wartości, jak i typu referencyjnego.
KFL
@KFL: W przypadku typów wartości niepodlegających wartości null prawdopodobnie też bym tego użył - ale jest to bardziej rozwlekłe w przypadku przypadków dopuszczających wartość null.
Jon Skeet
Niesamowita kontrola nad typami zwracanych, gdy wartość domyślna wynosi null .. :)
Sundara Prabu
„Nie. Albo raczej, zawsze zwraca domyślną wartość dla typu elementu…” - Właściwie to zrobiło to za mnie ... ponieważ również błędnie zrozumiałem znaczenie nazwy funkcji, zakładając, że w razie potrzeby możesz podać dowolną wartość domyślną
Jesus Campon
Bardzo podobało mi się to podejście, ale zmieniłem „T alternate” na „Func <T> alternate”, a następnie „return alternate ();” w ten sposób nie generuję dodatkowego obiektu, chyba że muszę. Jest to szczególnie przydatne, jeśli funkcja jest wywoływana wiele razy z rzędu, konstruktor działa wolno lub alternatywna instancja typu zajmuje dużo pamięci.
Dan Violet Sagmiller
64

Możesz użyć DefaultIfEmpty, a następnie First :

T customDefault = ...;
IEnumerable<T> mySequence = ...;
mySequence.DefaultIfEmpty(customDefault).First();
Witamina C
źródło
Uwielbiam pomysł DefaultIfEmpty- to działa ze wszystkimi API, które potrzebują domyślny zostać określone: First(), Last(), itd. Jako użytkownik nie musi pamiętać API, które pozwalają określić domyślne, które nie. Bardzo elegancko!
KFL
To jest w dużej mierze odpowiedź gniazda.
Jesse Williams,
19

Z dokumentacji FirstOrDefault

[Returns] default (TSource), jeśli źródło jest puste;

Z dokumentacji domyślnej (T) :

domyślne słowo kluczowe, które zwróci wartość null dla typów referencyjnych i zero dla liczbowych typów wartości. W przypadku struktur zwróci każdy element członkowski struktury zainicjowany na zero lub null w zależności od tego, czy są to typy wartości, czy odwołania. W przypadku typów wartości dopuszczających wartość null, default zwraca System.Nullable, który jest inicjowany jak każda struktura.

W związku z tym wartością domyślną może być null lub 0 w zależności od tego, czy typ jest odwołaniem, czy typem wartości, ale nie można kontrolować zachowania domyślnego.

RB.
źródło
6

Skopiowano z komentarza @sloth

Zamiast tego YourCollection.FirstOrDefault()możesz użyć YourCollection.DefaultIfEmpty(YourDefault).First()na przykład.

Przykład:

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)),
    };
Matas Vaitkevicius
źródło
2
Zwróć uwagę, że DefaultIfEmptyzwraca wartość domyślną JEŻELI kolekcja jest pusta (zawiera 0 elementów). Jeśli użyjesz FirstZ pasującym wyrażeniem, jak w twoim przykładzie, a ten warunek nie znajdzie żadnego elementu, zwrócona wartość będzie pusta.
OriolBG
5

Ty też możesz to zrobić

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

To używa tylko linq - yipee!

BurnWithLife
źródło
2
Jedyną różnicą między tą odpowiedzią a odpowiedzią witaminy C jest to, że ta używa FirstOrDefaultzamiast First. Według msdn.microsoft.com/en-us/library/bb340482.aspx , zalecane użycie toFirst
Daniel
5

Właściwie używam dwóch podejść, aby uniknąć NullReferenceExceptionpodczas pracy z kolekcjami:

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

Dla języka C # 6.0 lub nowszego:

Użyj ?.lub, ?[aby sprawdzić, czy jest null przed wykonaniem dostępu do elementu członkowskiego dokumentacji operatorów warunkowych

Przykład: var barCSharp6 = list.FirstOrDefault()?.Bar;

C # starsza wersja:

Służy DefaultIfEmpty()do pobierania wartości domyślnej, jeśli sekwencja jest pusta.Dokumentacja MSDN

Przykład: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;

Samuel Diogo
źródło
1
Operator propagacji zerowej nie jest dozwolony w lambie drzewa wyrażeń.
Lars335
1

Zamiast tego YourCollection.FirstOrDefault()możesz użyć YourCollection.DefaultIfEmpty(YourDefault).First()na przykład.

Radża
źródło
Jeśli uważasz, że było to pomocne, możesz głosować za. To nie jest odpowiedź.
jannagy02
1

Właśnie miałem podobną sytuację i szukałem rozwiązania, które pozwoli mi zwrócić alternatywną wartość domyślną bez zajmowania się nią po stronie dzwoniącego za każdym razem, gdy jej potrzebuję. To, co zwykle robimy, jeśli Linq nie obsługuje tego, czego chcemy, to napisanie nowego rozszerzenia, które się tym zajmie. To jest to co zrobiłem. Oto co wymyśliłem (choć nie przetestowałem):

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);
    }
}
harri
źródło
0

Wiem, że minęło trochę czasu, ale dodam do tego, w oparciu o najpopularniejszą odpowiedź, ale z małym rozszerzeniem, chciałbym udostępnić poniżej:

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

To pozwala mi nazwać to w tekście jako takim na moim własnym przykładzie, z którym miałem problemy:

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

Tak więc chciałem, aby domyślny program rozpoznawania nazw był używany w tekście, mogę wykonać zwykłe sprawdzenie, a następnie przekazać funkcję, aby klasa nie była tworzona, nawet jeśli nie jest używana, jest to funkcja do wykonania w razie potrzeby!

Aaron Gibson
źródło
-2

Użyj DefaultIfEmpty()zamiast FirstOrDefault().

Abhishek Singh
źródło