FirstOrDefault: valore predefinito diverso da null


142

A quanto ho capito, in Linq il metodo FirstOrDefault()può restituire un Defaultvalore diverso da null. Quello che non ho capito è che tipo di cose diverse da null possono essere restituite da questo (e simili) metodo quando non ci sono elementi nel risultato della query. C'è un modo particolare in cui questo può essere impostato in modo tale che se non esiste un valore per una determinata query, viene restituito un valore predefinito come valore predefinito?


147
Invece di YourCollection.FirstOrDefault(), potresti usare YourCollection.DefaultIfEmpty(YourDefault).First()ad esempio.
bradipo

6
Ho cercato qualcosa come il commento sopra per un po ', mi ha aiutato immensamente. Questa dovrebbe essere la risposta accettata.
Brandon,

Il commento sopra è la risposta migliore.
Tom Padilla,

Nel mio caso la risposta di @sloth non ha funzionato quando il valore restituito è nullable e assegnato a un non nullable. L'ho usato MyCollection.Last().GetValueOrDefault(0)per quello. Altrimenti la risposta di @Jon Skeet di seguito è IMO corretta.
Jnr

Risposte:


46

Caso generale, non solo per i tipi di valore:

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

Ancora una volta, questo non può davvero dire se ci fosse qualcosa nella sequenza, o se il primo valore è il default.

Se ti interessa, potresti fare qualcosa del genere

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

e usa come

var result = query.FirstOr(otherDefaultValue);

sebbene, come sottolinea Mr. Steak, questo potrebbe essere fatto altrettanto bene .DefaultIfEmpty(...).First().


I tuoi metodi generici hanno bisogno <T>nei loro nomi, ma più grave è che value == default(T)non funziona (perché chissà se Tpuò essere paragonato per l'uguaglianza?)
AakashM

Grazie per averlo sottolineato, @AakashM; In realtà ci ho provato ora e penso che dovrebbe essere OK (anche se non mi piace la boxe per i tipi di valore).
Rawling,

3
@Rawling Utilizzare EqualityComparer<T>.Default.Equals(value, default(T))per evitare il pugilato ed evitare un'eccezione se il valore ènull
Lukazoid

199

A quanto ho capito, in Linq il metodo FirstOrDefault () può restituire un valore predefinito diverso da null.

No. O meglio, restituisce sempre il valore predefinito per il tipo di elemento ... che è un riferimento null, il valore null di un tipo di valore annullabile o il valore naturale "tutti gli zero" per un tipo di valore non annullabile.

C'è un modo particolare in cui questo può essere impostato in modo tale che se non esiste un valore per una determinata query, viene restituito un valore predefinito come valore predefinito?

Per i tipi di riferimento, puoi semplicemente usare:

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

Naturalmente questo ti darà anche l '"altro valore predefinito" se è presente il primo valore, ma è un riferimento null ...


So che la domanda richiede un tipo di riferimento, ma la tua soluzione non funziona quando gli elementi sono di tipo valore int. Preferisco di gran lunga l'uso di DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Funziona sia per il tipo di valore che per il tipo di riferimento.
KFL

@KFL: Per tipi di valore non annullabili probabilmente lo userò anch'io, ma è più complicato per i casi nullable.
Jon Skeet,

Fantastico controllo sui tipi restituiti quando il valore predefinito è null .. :)
Sundara Prabu,

"No. O meglio, restituisce sempre il valore predefinito per il tipo di elemento ..." - Lo ha fatto per me, in realtà .. poiché ho anche frainteso il significato del nome della funzione assumendo che tu possa fornire qualsiasi valore predefinito quando richiesto
Jesus Campon,

Mi è piaciuto molto questo approccio, ma ho cambiato "T alternate" con "Func <T> alternate", e quindi "return alternate ();" in questo modo non generi l'oggetto extra a meno che non sia necessario. Particolarmente utile se la funzione viene chiamata più volte di seguito, il costruttore è lento o l'istanza alternativa del tipo occupa molta memoria.
Dan Violet Sagmiller,

64

Puoi usare DefaultIfEmpty seguito da First :

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

Mi piace l'idea di DefaultIfEmpty- funziona con tutte le API che necessitano di un difetto da specificare: First(), Last(), ecc Come utente, non è necessario ricordare che le API permettono di specificare default che non lo fanno. Molto elegante!
KFL

Questa è davvero la risposta del nido.
Jesse Williams,

19

Dalla documentazione per FirstOrDefault

[Restituisce] impostazione predefinita (TSource) se l'origine è vuota;

Dalla documentazione per impostazione predefinita (T) :

la parola chiave predefinita, che restituirà null per i tipi di riferimento e zero per i tipi di valore numerici. Per le strutture, restituirà ogni membro della struttura inizializzato su zero o null a seconda che si tratti di valori o tipi di riferimento. Per i tipi di valore nullable, il valore predefinito restituisce System.Nullable, che viene inizializzato come qualsiasi struttura.

Pertanto, il valore predefinito può essere nullo o 0 a seconda che il tipo sia un riferimento o un tipo di valore, ma non è possibile controllare il comportamento predefinito.


6

Copiato dal commento di @sloth

Invece di YourCollection.FirstOrDefault() , potresti usare YourCollection.DefaultIfEmpty(YourDefault).First()ad esempio.

Esempio:

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
Nota che DefaultIfEmptyrestituisce il valore predefinito SE la raccolta è vuota (ha 0 elementi). Se usi FirstWITH con un'espressione corrispondente come nel tuo esempio e quella condizione non trova alcun articolo, il tuo valore di ritorno sarà vuoto.
OriolBG

5

Puoi anche farlo

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

Questo utilizza solo linq - yipee!


2
L'unica differenza tra questa risposta e la risposta della vitamina C è che questa utilizza FirstOrDefaultinvece di First. Secondo msdn.microsoft.com/en-us/library/bb340482.aspx , l'uso raccomandato èFirst
Daniel

5

In realtà, uso due approcci da evitare NullReferenceExceptionquando lavoro con le raccolte:

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

Per C # 6.0 o successivo:

Utilizzare ?.o ?[per verificare se è null prima di eseguire l'accesso a un membro alla documentazione Operatori null condizionale

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

C # versione precedente:

Utilizzare DefaultIfEmpty()per recuperare un valore predefinito se la sequenza è vuota. Documentazione MSDN

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


1
L'operatore di propagazione null non è consentito nella lamba dell'albero delle espressioni.
Lars335,

1

Invece diYourCollection.FirstOrDefault() , potresti usare YourCollection.DefaultIfEmpty(YourDefault).First()ad esempio.


Quando pensi che sia stato utile, puoi votare. Questa non è una risposta
jannagy02,

1

Ho appena avuto una situazione simile e stavo cercando una soluzione che mi permettesse di restituire un valore predefinito alternativo senza occuparmene dal lato chiamante ogni volta che ne ho bisogno. Quello che facciamo di solito nel caso in cui Linq non supporti ciò che vogliamo, è scrivere una nuova estensione che se ne occupi. Questo è quello che ho fatto. Ecco cosa mi è venuto in mente (ma non testato):

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

So che è passato un po 'di tempo, ma aggiungo questo, basato sulla risposta più popolare ma con un piccolo ID di estensione come condividere quanto segue:

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

Questo mi permette di chiamarlo in linea come tale con il mio esempio che avevo problemi con:

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

Quindi per me volevo solo usare un resolver predefinito in linea, posso fare il mio solito controllo e quindi passare una funzione in modo che una classe non sia istanziata anche se non utilizzata, è invece una funzione da eseguire quando richiesto!


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.