FirstOrDefault: valeur par défaut autre que null


142

Si je comprends bien, dans Linq, la méthode FirstOrDefault()peut renvoyer une Defaultvaleur autre que null. Ce que je n'ai pas déterminé, c'est quel genre de choses autres que null peuvent être retournées par cette méthode (et similaire) lorsqu'il n'y a aucun élément dans le résultat de la requête. Existe-t-il un moyen particulier de configurer cela de sorte que s'il n'y a pas de valeur pour une requête particulière, une valeur prédéfinie soit renvoyée comme valeur par défaut?


147
Au lieu de YourCollection.FirstOrDefault(), vous pouvez utiliser YourCollection.DefaultIfEmpty(YourDefault).First()par exemple.
paresseux

6
Je cherchais quelque chose comme le commentaire ci-dessus depuis un certain temps, cela m'a énormément aidé. Cela devrait être la réponse acceptée.
Brandon

Le commentaire ci-dessus est la meilleure réponse.
Tom Padilla

Dans mon cas, la réponse @sloth ne fonctionnait pas lorsque la valeur retournée est nullable et affectée à une non nullable. Je l'ai utilisé MyCollection.Last().GetValueOrDefault(0)pour ça. Sinon, la réponse de @Jon Skeet ci-dessous est correcte pour l'OMI.
Jnr

Réponses:


46

Cas général, pas seulement pour les types valeur:

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

Encore une fois, cela ne peut pas vraiment dire s'il y avait quelque chose dans votre séquence ou si la première valeur était la valeur par défaut.

Si cela vous intéresse, vous pouvez faire quelque chose comme

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

et utiliser comme

var result = query.FirstOr(otherDefaultValue);

bien que, comme le souligne M. Steak, cela pourrait être fait aussi bien par .DefaultIfEmpty(...).First().


Vos méthodes génériques ont besoin <T>de leurs noms, mais le plus grave est que value == default(T)cela ne fonctionne pas (car qui sait si Tpeut être comparé pour l'égalité?)
AakashM

Merci de l'avoir signalé, @AakashM; J'ai en fait essayé cela maintenant et je pense que ça devrait être OK (bien que je n'aime pas la boxe pour les types valeur).
Rawling

3
@Rawling Utilisez EqualityComparer<T>.Default.Equals(value, default(T))pour éviter la boxe et éviter une exception si la valeur estnull
Lukazoid

199

Si je comprends bien, dans Linq, la méthode FirstOrDefault () peut renvoyer une valeur par défaut autre que null.

Non. Ou plutôt, il renvoie toujours la valeur par défaut pour le type d'élément ... qui est soit une référence nulle, la valeur nulle d'un type de valeur Nullable, ou la valeur naturelle "tous les zéros" pour un type valeur non Nullable.

Existe-t-il un moyen particulier de configurer cela de sorte que s'il n'y a pas de valeur pour une requête particulière, une valeur prédéfinie soit renvoyée comme valeur par défaut?

Pour les types de référence, vous pouvez simplement utiliser:

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

Bien sûr, cela vous donnera également "l'autre valeur par défaut" si la première valeur est présente, mais est une référence nulle ...


Je sais que la question demande un type de référence, mais votre solution ne fonctionne pas lorsque les éléments sont des types de valeur comme int. Je préfère de beaucoup l'utilisation de DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Fonctionne à la fois pour le type de valeur et le type de référence.
KFL

@KFL: Pour les types de valeur non Nullable, j'utiliserais probablement cela aussi - mais c'est plus long pour les cas Nullable.
Jon Skeet

Contrôle impressionnant sur les types de retour lorsque la valeur par défaut est nulle .. :)
Sundara Prabu

"Non. Ou plutôt, il renvoie toujours la valeur par défaut pour le type d'élément ..." - Cela l'a fait pour moi, en fait ... car j'ai également mal compris la signification du nom de la fonction en supposant que vous pourriez fournir une valeur par défaut si nécessaire
Jesus Campon

J'ai vraiment aimé cette approche, mais j'ai changé le "T Alternate" avec "Func <T> Alternate", puis "return Alternate ();" de cette façon, je ne génère pas l'objet supplémentaire à moins que j'en ai besoin. Particulièrement utile si la fonction est appelée plusieurs fois de suite, si le constructeur est lent ou si l'instance alternative du type prend beaucoup de mémoire.
Dan Violet Sagmiller

64

Vous pouvez utiliser DefaultIfEmpty suivi de First :

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

J'aime l'idée de DefaultIfEmpty- cela fonctionne avec les API ALL qui ont besoin d' un défaut à préciser: First(), Last(), etc. En tant qu'utilisateur, vous n'avez pas besoin de se rappeler que les API permettent de spécifier par défaut qui ne le font pas. Très élégant!
KFL

C'est vraiment la réponse du nid.
Jesse Williams

19

À partir de la documentation de FirstOrDefault

[Renvoie] default (TSource) si la source est vide;

À partir de la documentation par défaut (T) :

le mot-clé par défaut, qui renverra null pour les types référence et zéro pour les types valeur numérique. Pour les structures, il retournera chaque membre de la structure initialisé à zéro ou à null selon qu'il s'agit de types valeur ou référence. Pour les types de valeur Nullable, default renvoie un System.Nullable, qui est initialisé comme n'importe quelle structure.

Par conséquent, la valeur par défaut peut être nulle ou 0 selon que le type est un type de référence ou de valeur, mais vous ne pouvez pas contrôler le comportement par défaut.


6

Copié du commentaire de @sloth

Au lieu de YourCollection.FirstOrDefault(), vous pouvez utiliser YourCollection.DefaultIfEmpty(YourDefault).First()par exemple.

Exemple:

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
Notez que DefaultIfEmptyrenvoie la valeur par défaut SI la collection est vide (contient 0 élément). Si vous utilisez FirstWITH une expression correspondante comme dans votre exemple et que cette condition ne trouve aucun élément, votre valeur de retour sera vide.
OriolBG

5

Vous pouvez également le faire

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

Cela utilise uniquement linq - yipee!


2
La seule différence entre cette réponse et la réponse de la vitamine C est que celle-ci utilise à la FirstOrDefaultplace de First. Selon msdn.microsoft.com/en-us/library/bb340482.aspx , l'utilisation recommandée estFirst
Daniel

5

En fait, j'utilise deux approches à éviter NullReferenceExceptionlorsque je travaille avec des collections:

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

Pour C # 6.0 ou version ultérieure:

Utilisez ?.ou ?[pour tester si est null avant d'effectuer un accès membre à la documentation des opérateurs à condition nulle

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

Ancienne version C #:

Utilisez DefaultIfEmpty()pour récupérer une valeur par défaut si la séquence est vide. Documentation MSDN

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


1
L'opérateur de propagation nul n'est pas autorisé dans l'arborescence d'expression lamba.
Lars335

1

Au lieu de YourCollection.FirstOrDefault(), vous pouvez utiliser YourCollection.DefaultIfEmpty(YourDefault).First()par exemple.


Lorsque vous pensez que cela a été utile, vous pouvez voter pour. Ceci n'est pas une réponse.
jannagy02

1

Je viens d'avoir une situation similaire et je cherchais une solution qui me permette de renvoyer une valeur par défaut alternative sans m'en occuper du côté appelant à chaque fois que j'en ai besoin. Ce que nous faisons habituellement au cas où Linq ne prend pas en charge ce que nous voulons, c'est écrire une nouvelle extension qui s'en charge. C'est ce que j'ai fait. Voici ce que j'ai trouvé (pas testé cependant):

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

Je sais que cela fait un moment mais j'ajouterai à cela, basé sur la réponse la plus populaire, mais avec une petite extension, j'aimerais partager ce qui suit:

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

Cela me permet de l'appeler en ligne comme tel avec mon propre exemple avec lequel j'avais des problèmes:

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

Donc, pour moi, je voulais juste qu'un résolveur par défaut soit utilisé en ligne, je peux faire ma vérification habituelle puis passer une fonction pour qu'une classe ne soit pas instanciée même si elle n'est pas utilisée, c'est une fonction à exécuter lorsque cela est nécessaire à la place!


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.