FirstOrDefault: Ein anderer Standardwert als null

142

Soweit ich weiß, FirstOrDefault()kann die Methode in Linq einen anderen DefaultWert als null zurückgeben. Was ich nicht herausgefunden habe, ist, welche anderen Dinge als null von dieser (und ähnlichen) Methode zurückgegeben werden können, wenn das Abfrageergebnis keine Elemente enthält. Gibt es eine bestimmte Möglichkeit, dies so einzurichten, dass, wenn für eine bestimmte Abfrage kein Wert vorhanden ist, ein vordefinierter Wert als Standardwert zurückgegeben wird?

Sachin Kainth
quelle
147
Stattdessen YourCollection.FirstOrDefault()könnten Sie YourCollection.DefaultIfEmpty(YourDefault).First()zum Beispiel verwenden.
Faultier
6
Ich habe eine ganze Weile nach so etwas wie dem obigen Kommentar gesucht, es hat mir sehr geholfen. Dies sollte die akzeptierte Antwort sein.
Brandon
Der obige Kommentar ist die beste Antwort.
Tom Padilla
In meinem Fall hat die @ sloth-Antwort nicht funktioniert, wenn der zurückgegebene Wert nullwertfähig und einem nicht nullwertfähig zugewiesen ist. Ich habe dafür verwendet MyCollection.Last().GetValueOrDefault(0). Andernfalls ist die Antwort von @Jon Skeet unten IMO korrekt.
Jnr

Antworten:

46

Allgemeiner Fall, nicht nur für Werttypen:

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

Auch dies kann sagen , nicht wirklich , ob es war alles in Ihrer Sequenz, oder wenn der erste Wert der Standard ist.

Wenn Sie sich dafür interessieren, könnten Sie so etwas tun

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

und verwenden als

var result = query.FirstOr(otherDefaultValue);

obwohl, wie Mr. Steak betont, dies genauso gut von getan werden könnte .DefaultIfEmpty(...).First().

Rawling
quelle
Ihre generischen Methoden müssen <T>in ihren Namen, aber schwerwiegender ist, dass value == default(T)das nicht funktioniert (denn wer weiß, ob Tfür Gleichheit verglichen werden kann?)
AakashM
Vielen Dank für den Hinweis, @AakashM; Ich habe das jetzt tatsächlich versucht und ich denke, es sollte in Ordnung sein (obwohl ich das Boxen für Werttypen nicht mag).
Rawling
3
@Rawling Verwenden Sie EqualityComparer<T>.Default.Equals(value, default(T)), um das Boxen zu vermeiden und eine Ausnahme zu vermeiden, wenn der Wertnull
Lukazoid
199

Soweit ich weiß, kann die Methode FirstOrDefault () in Linq einen Standardwert von etwas anderem als null zurückgeben.

Nein. Oder besser gesagt, es wird immer der Standardwert für den Elementtyp ... zurückgegeben, der entweder eine Nullreferenz, der Nullwert eines nullbaren Werttyps oder der natürliche Wert "alle Nullen" für einen nicht nullbaren Werttyp ist.

Gibt es eine bestimmte Möglichkeit, dies so einzurichten, dass, wenn für eine bestimmte Abfrage kein Wert vorhanden ist, ein vordefinierter Wert als Standardwert zurückgegeben wird?

Für Referenztypen können Sie einfach Folgendes verwenden:

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

Dies gibt Ihnen natürlich auch den "anderen Standardwert", wenn der erste Wert vorhanden ist, aber eine Nullreferenz ist ...

Jon Skeet
quelle
Ich weiß, dass die Frage nach dem Referenztyp fragt, aber Ihre Lösung funktioniert nicht, wenn Elemente Werttypen wie sind int. Ich bevorzuge die Verwendung von DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Funktioniert sowohl für den Werttyp als auch für den Referenztyp.
KFL
@KFL: Für nicht nullbare Werttypen würde ich das wahrscheinlich auch verwenden - aber für nullbare Fälle ist es langwieriger.
Jon Skeet
Fantastische Kontrolle über Rückgabetypen, wenn der Standardwert null ist .. :)
Sundara Prabu
"Nein. Oder besser gesagt, es wird immer der Standardwert für den Elementtyp zurückgegeben ..." - Dies hat es tatsächlich für mich getan ... da ich auch die Bedeutung des Funktionsnamens falsch verstanden habe, indem ich davon ausgegangen bin, dass Sie bei Bedarf einen beliebigen Standardwert angeben können
Jesus Campon
Dieser Ansatz hat mir sehr gut gefallen, aber ich habe das "T-Alternate" durch "Func <T> Alternate" und dann "Return Alternate ()" geändert. Auf diese Weise generiere ich das zusätzliche Objekt nur, wenn ich es brauche. Besonders nützlich, wenn die Funktion mehrmals hintereinander aufgerufen wird, der Konstruktor langsam ist oder die alternative Instanz des Typs viel Speicher benötigt.
Dan Violet Sagmiller
64

Sie können DefaultIfEmpty gefolgt von First verwenden :

T customDefault = ...;
IEnumerable<T> mySequence = ...;
mySequence.DefaultIfEmpty(customDefault).First();
Vitamin C
quelle
Ich liebe die Idee von DefaultIfEmpty- es funktioniert mit allen APIs , die eine Standard müssen angegeben werden: First(), Last()usw. Als Benutzer, müssen Sie sich nicht daran erinnern , welche APIs ermöglichen angeben Standard die dies nicht tun. Sehr elegant!
KFL
Dies ist sehr viel die Nestantwort.
Jesse Williams
19

Aus der Dokumentation zu FirstOrDefault

[Rückgabe] Standard (TSource), wenn die Quelle leer ist;

Aus der Dokumentation für Standard (T) :

Das Standardschlüsselwort, das für Referenztypen null und für numerische Werttypen null zurückgibt. Bei Strukturen wird jedes Element der Struktur auf Null oder Null zurückgesetzt, je nachdem, ob es sich um Wert- oder Referenztypen handelt. Bei nullbaren Werttypen gibt default standardmäßig ein System.Nullable zurück, das wie jede Struktur initialisiert wird.

Daher kann der Standardwert null oder 0 sein, je nachdem, ob der Typ ein Referenz- oder ein Werttyp ist. Sie können das Standardverhalten jedoch nicht steuern.

RB.
quelle
6

Aus dem Kommentar von @sloth übernommen

Stattdessen YourCollection.FirstOrDefault()könnten Sie YourCollection.DefaultIfEmpty(YourDefault).First()zum Beispiel verwenden.

Beispiel:

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
quelle
2
Beachten Sie, dass DefaultIfEmptyder Standardwert zurückgegeben wird, wenn die Sammlung leer ist (0 Elemente enthält). Wenn Sie FirstWITH mit einem übereinstimmenden Ausdruck wie in Ihrem Beispiel verwenden und diese Bedingung kein Element findet, ist Ihr Rückgabewert leer.
OriolBG
5

Sie können dies auch tun

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

Dies verwendet nur linq - yipee!

BurnWithLife
quelle
2
Der einzige Unterschied zwischen dieser Antwort und der Antwort von Vitamin C besteht darin, dass diese FirstOrDefaultanstelle von verwendet wird First. Laut msdn.microsoft.com/en-us/library/bb340482.aspx ist die empfohlene VerwendungFirst
Daniel
5

Eigentlich verwende ich zwei Ansätze, um zu vermeiden, NullReferenceExceptionwenn ich mit Sammlungen arbeite:

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

Für C # 6.0 oder höher:

Verwenden Sie ?.oder ?[, um zu testen, ob null ist, bevor Sie einen Mitgliederzugriff durchführen. Dokumentation zu nullbedingten Operatoren

Beispiel: var barCSharp6 = list.FirstOrDefault()?.Bar;

C # ältere Version:

Verwenden Sie DefaultIfEmpty()diese Option , um einen Standardwert abzurufen, wenn die Sequenz leer ist. MSDN-Dokumentation

Beispiel: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;

Samuel Diogo
quelle
1
Der Null-Propagierungsoperator ist in Ausdrucksbaum-Lamba nicht zulässig.
Lars335
1

Stattdessen YourCollection.FirstOrDefault()könnten Sie YourCollection.DefaultIfEmpty(YourDefault).First()zum Beispiel verwenden.

Raj.A
quelle
Wenn Sie denken, dass es hilfreich war, können Sie upvoten. Dies ist keine Antwort.
jannagy02
1

Ich hatte gerade eine ähnliche Situation und suchte nach einer Lösung, mit der ich einen alternativen Standardwert zurückgeben kann, ohne mich jedes Mal auf der Anruferseite darum zu kümmern, wenn ich ihn benötige. Was wir normalerweise tun, falls Linq nicht unterstützt, was wir wollen, ist eine neue Erweiterung zu schreiben, die sich darum kümmert. Das ist, was ich tat. Folgendes habe ich mir ausgedacht (allerdings nicht getestet):

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
quelle
0

Ich weiß, dass es eine Weile her ist, aber ich werde es ergänzen, basierend auf der beliebtesten Antwort, aber mit einer kleinen Erweiterung möchte ich Folgendes teilen:

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

Dies ermöglicht es mir, es als solches mit meinem eigenen Beispiel zu bezeichnen, mit dem ich Probleme hatte:

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

Für mich wollte ich nur, dass ein Standard-Resolver inline verwendet wird. Ich kann meine übliche Prüfung durchführen und dann eine Funktion übergeben, damit eine Klasse nicht instanziiert wird, auch wenn sie nicht verwendet wird. Stattdessen kann sie bei Bedarf ausgeführt werden.

Aaron Gibson
quelle
-2

Verwenden Sie DefaultIfEmpty()anstelle von FirstOrDefault().

Abhishek Singh
quelle