2009-11-10 3 views
9

La méthode d'extension IEnumerable FirstOrDefault n'a pas fait exactement ce que je voulais, donc j'ai créé FirstOrValue. Est-ce un bon moyen d'y parvenir ou existe-t-il un meilleur moyen?Une prise de vue différente sur FirstOrDefault

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, T value) 
{ 
    T first = source.FirstOrDefault(predicate); 
    return Equals(first, default(T)) ? value : first; 
} 

Répondre

39

Votre code est probablement incorrect; vous n'avez probablement pas considéré tous les cas.

Bien sûr, nous ne pouvons pas savoir si un code est correct ou incorrect jusqu'à ce que nous ayons une spécification. Commencez par écrire une spécification d'une ligne:

"FirstOrValue<T> prend une séquence de T, un prédicat et une valeur de T, et renvoie le premier élément de la séquence qui correspond au prédicat s'il y en a un, ou , s'il n'y en a pas, la valeur déclarée. "

Est-ce que votre tentative implémente réellement cette spécification? Certainement pas! Testez-le:

int x = FirstOrValue<int>(new[] { -2, 0, 1 }, y=>y*y==y, -1); 

cela renvoie -1. La réponse correcte selon la spécification est 0. Le premier élément qui correspond au prédicat est zéro, il doit donc être retourné.

Une bonne mise en œuvre de la spécification ressemblerait à ceci:

public static T FirstOrValue<T>(this IEnumerable<T> sequence, Func<T, bool> predicate, T value) 
{ 
    if (sequence == null) throw new ArgumentNullException("sequence"); 
    if (predicate == null) throw new ArgumentNullException("predicate"); 
    foreach(T item in sequence) 
     if (predicate(item)) return item; 
    return value; 
} 

toujours écrire une spécification d'abord, même si elle est seulement une seule phrase.

+0

Cela devrait faire partie du Cadre! Ou ai-je négligé cela? – Marcel

+2

+1 pour "Toujours écrire une spécification en premier". –

5

default(T) retournera null par défaut pour les types de référence.

Je ferais ce

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, T value) 
{ 
    T first = source.FirstOrDefault(predicate); 
    return first ?? value; 
} 
+4

Je vais l'écrire sur une seule ligne: source.FirstOrDefault de retour (prédicat) ?? valeur; – Zote

+0

points supplémentaires pour la coalescence nulle –

+1

@zote: le faire de manière daniels rend plus facile d'attacher un débogueur et voir ce qui se passe –

-1

semble raisonnable pour moi si vous souhaitez modifier la lisibilité au lieu d'utiliser DefaultIfEmpty.

Vous pouvez également créer un remplacement utilisant un lambda si la création de la valeur par défaut est coûteuse, en la créant uniquement si nécessaire.

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T> getValue) 
{ 
    T first = source.FirstOrDefault(predicate); 
    return Equals(first, default(T)) ? getValue() : first; 
} 
+0

Très joli, l'ajout d'un lambda le rend encore plus utile. – Aurequi

0

Comme il s'agit d'une surcharge, il vaut la peine de mentionner la version sans prédicat.

public static T FirstOrValue<T>(this IEnumerable<T> sequence, T value) 
{ 
    if (sequence == null) throw new ArgumentNullException("sequence"); 
    foreach(T item in sequence) 
     return item; 
    return value; 
} 
+2

Supposons que la séquence comporte un million d'éléments et aucune propriété Count. Vous devriez en énumérer * tous * pour déterminer qu'il n'y en a pas * aucun *. Si vous avez un million de pièces de monnaie dans un pot, et que vous souhaitez savoir si le pot est vide, comptez-vous les pièces de un cent? C'est une très, très mauvaise technique que vous utilisez ici. (Je ne comprends pas non plus pourquoi vous répondez aux questions d'une question vieille de six ans qui a déjà été acceptée correctement et qui, en fait, ne répondent même pas à la question initiale, peut-être pourriez-vous ajouter plus de valeur sur le site? –

+0

Je cherchais FirstOrValue et je suis tombé sur votre réponse charmante. J'ai copié votre code et il m'est apparu qu'il y avait une surcharge. Collé ceci ici au cas où d'autres ont traversé le même processus de pensée. Peut-être pourriez-vous suggérer une meilleure façon de le faire. – djv

+1

Eh bien, puisque votre méthode proposée est la même que celle que j'ai écrite sauf sans prédicat, ma suggestion serait d'écrire la même méthode sans prédicat: public static T FirstOrValue (cette séquence IEnumerable , valeur T) { if (sequence == null) throw new ArgumentNullException ("séquence"); foreach (élément T dans l'ordre) return item; valeur de retour; } ' –

Questions connexes