Est-il déraisonnable de s’attendre à ce que Any () * not * lève une exception de référence nulle?

When you create an extension method you can, of course, call it on null.But, unlike an instance method call, calling it on null doesn't have to throw a NullReferenceException -> you have to check and throw it manually.

Pour l'implémentation de la méthode d'extension Linq Any() , Microsoft a décidé de lancer une ArgumentNullException ( https://github.com/dotnet/corefx/blob/master/src/System.Linq/src /System/Linq/AnyAll.cs ).

It irks me to have to write if( myCollection != null && myCollection.Any() )

Ai-je tort, en tant que client de ce code, de s'attendre à ce ((int []) null) .Any() doit renvoyer false ?

40
Deux réflexions: 1. Peut-être que votre conception globale pourrait être retravaillée afin que vous n'ayez pas à effectuer de vérification supplémentaire null ? En d'autres termes, les méthodes qui renvoient IEnumerable ne doivent jamais renvoyer null , mais plutôt une collection vide s'il s'agit de la condition. 2. Créez une méthode d'extension et utilisez-la à la place: public static bool IsNullOrEmpty (cette source IEnumerable ) {return source? .Any ()! = True; }
ajouté l'auteur Will Dean, source
Je dois souligner que chaque méthode LINQ unique jette un argument nul. Any est juste cohérent.
ajouté l'auteur Allen Bargi, source
@thisextendsthat je suis curieux de voir à quoi ressemble le code qui renvoie une collection null. Je pense que le problème ne réside pas dans le comportement de Any() , mais dans le comportement de la méthode ou de la propriété dont vous récupérez la valeur null. Il n'y a vraiment jamais de bonne raison pour qu'un membre qui retourne une collection retourne la valeur null.
ajouté l'auteur hao, source
Ceci est trop critique pour ce site, cependant je suis toujours ennuyé de vérifier null lorsque je traite une collection null et une collection vide exactement de la même manière: ne faites rien avec elle. Toutefois, cela se produit lorsqu'un pointeur NULL est significatif par rapport à une collection vide (masquer les résultats de la recherche si null par rapport à "Votre recherche n'a renvoyé aucun résultat").
ajouté l'auteur DenNukem, source
Notez que CA2201 considère l’exception NullReferenceException comme étant réservée au moteur d’exécution. Il ne devrait être jeté que lorsque null est réellement déréférencé. Vous ne devriez pas le jeter explicitement à partir de votre propre code.
ajouté l'auteur Marcos, source
Question équivalente sur l'objet SO: Pourquoi Any() ne fonctionne-t-il pas? ac # objet null
ajouté l'auteur user8669, source
@SebastianRedl: Même avant LINQ, la boucle C# foreach s'étouffait dans les collections null afin que vous puissiez compter cela vers la "cohérence".
ajouté l'auteur dan04, source
Créer une méthode d'extension IList EmptyIfNull (cet IList que) {if (que == null) renvoie une nouvelle liste (); return that;} (Ou IEnumerable) Ensuite, vous pouvez dire thing.EmptyIfNull (). Any()
ajouté l'auteur Alireza Abdollahi, source
@ JesseC.Slicer Cela dépend de la façon dont votre code est organisé. Si vous appelez une fonction qui renvoie IEnumerable et que vous la traitez immédiatement dans la même portée, vous pouvez compter sur la méthode qui se comporte conformément à son contrat. Cependant, s'il s'agit d'un argument à une autre fonction, cette fonction ne doit pas compter sur les appelants ne transmettant jamais null . Je ne voudrais certainement pas éviter de créer des fonctions supplémentaires pour ne pas avoir à prendre en compte le cas null . Donc, ce problème va inévitablement se poser à certains endroits.
ajouté l'auteur jmibanez, source
Complètement à part: n'écrivez même pas un code comme celui-ci Error.ArgumentNull . C'est vraiment terrible. Les vis avec la trace de la pile sont plus difficiles à lire. Il suffit de lancer l'exception en ligne.
ajouté l'auteur jmibanez, source
Même l'inventeur du pointeur/référence null l'appelle son erreur d'un milliard de dollars . D'après mon expérience, il existe toujours une meilleure solution qu'une référence nulle. De ce point de vue, faciliter l'utilisation d'une langue par null réduira la qualité du code qui sera écrit avec celle-ci.
ajouté l'auteur R. Schmitz, source
@ 17of26: == true est-il vraiment nécessaire?
ajouté l'auteur Kent, source
@ 17of26: Merci pour la réponse. Je pensais que null serait faux en C #.
ajouté l'auteur Kent, source
en C# 6, vous pouvez simplifier votre vérification si (myCollection? .Any() == true)
ajouté l'auteur Minihawk, source
@EricDuminil Oui, parce que l'expression 'myCollection? .Any ()' a trois états possibles: true, false, null
ajouté l'auteur Minihawk, source
Eh bien pourquoi est-il nul en premier lieu? Vous ne voulez pas que null soit considéré comme vide, car ce n'est pas vide, c'est quelque chose de complètement différent. Peut-être voulez-vous qu'il soit considéré comme vide dans votre cas, mais l'ajout de ce genre de chose à la langue conduit à des bugs.
ajouté l'auteur immibis, source
C'est pourquoi vous ne devez pas renvoyer null lorsque vous traitez avec des collections, mais utilisez plutôt une collection vide dans ces cas-là.
ajouté l'auteur Tobias Sette, source
N'oubliez pas qu'un La méthode d'extension est en fait une méthode statique qui est "ajoutée" à un type existant et reçoit l'instance d'appel en tant que paramètre. Ainsi, lorsque vous faites myCollection.Any() , vous faites réellement MyCollectionType.Any (myCollection) ; d'où la logique ArgumentNullException .
ajouté l'auteur Nate, source
Le fait d’avoir des types de référence Nullables devrait résoudre ce problème: msdn. microsoft.com/en-us/magazine/…
ajouté l'auteur Matthew, source
ce que @JoshPart a dit.
ajouté l'auteur Sqrs, source
Même dans F #, qui n'utilise pas vraiment les valeurs NULL sauf pour l'interopérabilité avec d'autres langages .NET, null |> Seq.isEmpty lève System.ArgumentNullException: la valeur ne peut pas être null . L'attente semble être que vous ne passerez pas de valeurs indéfinies pour quelque chose qui devrait exister, donc c'est toujours une exception lorsque vous avez un null. Si c'est une question d'initialisation, je commencerais par une séquence vide au lieu d'un null .
ajouté l'auteur Zach, source
Il y a des raisons de cohérence, mais explorons-nous du point de vue de l'EDD. Lorsqu'un développeur appelle Any sur une collection, il recherche des correspondances. Quel est l'intérêt de rechercher des correspondances sur une valeur null? Que se passe-t-il si votre collection est à tort erronée? Faut-il ajouter au problème? Quoi qu'il en soit, les questions déjà répondues dans le github;)
ajouté l'auteur user143241, source
À mon avis, le fait que lorsque les méthodes d'extension ont été ajoutées à C#, foo était certain de ne pas être null (en supposant qu'il n'y ait aucune manipulation concurrente) après foo.Bar (); a soudainement cessé d'être vrai, ce qui constitue un changement assez insidieux. De manière générale, je suis d’avis que vous devriez écrire des méthodes d’extension telles qu’elles se comportent comme des méthodes "réelles" et donc émettre une exception si le paramètre this est nul.
ajouté l'auteur ohias, source
@ jpmc26 le code throw Error.XXX est tout à fait correct et judicieux. La trace de pile n'est renseignée que lorsque l'exception est levée, il n'y a donc rien d'inhabituel dans les traces de pile. Refactoriser la création d’exceptions standard améliore la lisibilité et facilite les modifications futures.
ajouté l'auteur Johnbot, source

13 Réponses

J'ai un sac avec cinq pommes de terre dedans. Y at-il des pommes de terre .Any() dans le sac?

"Yes," you say. <= true

Je sors toutes les pommes de terre et les mange. Y at-il des pommes de terre .Any() dans le sac?

"No," you say. <= false

J'incinère complètement le sac dans un feu. Existe-t-il des pommes de terre .Any() dans le sac maintenant?

"There is no bag." <= ArgumentNullException

148
ajouté
Si ma main ( Sac de pommes de terre; ) ne contient rien ( pommes de terre = null; ), je la désigne et demande "Y at-il des pommes de terre dans ce sac?" ( if (potatos.Any ()) {} ). Je pense que vous auriez tout à fait raison de me regarder étrangement et de dire "Quel sac? Il n'y a pas de sac là-bas, crétin fou! Va-t'en!" (Lancer une exception).
ajouté l'auteur user109758, source
@IsmaelMiguel pas vraiment, il aurait pu brûler le sac avec les pommes de terre à l'intérieur, pour finir avec des pommes de terre frites et pas de sac :)
ajouté l'auteur jwenting, source
@IsmaelMiguel pourquoi ne pas mettre le feu au sac et l'utiliser pour cuire les pommes de terre?
ajouté l'auteur jwenting, source
@ D.BenKnoble: Sans avoir vu le coffre de ma voiture, êtes-vous prêt à témoigner qu'il n'y a pas de corps dans le coffre de ma voiture? Non? Quelle est la différence entre vérifier le coffre et ne rien trouver ou ne pas vérifier le coffre? Ne pouvez-vous pas simplement témoigner maintenant, puisque vous auriez vu exactement le même nombre de corps dans les deux cas? ... c'est la différence. Vous ne pouvez pas garantir quelque chose si vous n'êtes pas en mesure de le confirmer au départ. Vous supposez que les deux sont équivalents, mais ce n'est pas une donnée. Dans certains cas, il peut y avoir une différence importante entre les deux.
ajouté l'auteur Flater, source
Une meilleure analogie avec un sac spécifique qui a été détruit est si je vous demande s'il y a des pommes de terre dans le sac et que je ne vous ai tout simplement pas dit de quel sac je parle. Une variable Bag référence au sac que je demande. Si la variable est réellement null , alors je ne fais pas référence à une sorte de sac défectueux qui n'est plus capable de contenir des pommes de terre (ce qui pourrait raisonnablement être considéré comme ne contenant aucune pomme de terre), Je ne parviens pas à donner une référence pour un sac du tout. Ni true ni false n'est une réponse cohérente; la question est défectueuse.
ajouté l'auteur Ben, source
@jwenting Cela rompt l'ordre des événements décrits
ajouté l'auteur Stumps, source
@Oly Dan les a fait cuire au feu, avant de manger et d'utiliser ce feu pour incinérer le sac.
ajouté l'auteur Stumps, source
@Oly Eh bien, cela ne dit nulle part qu'il s'agissait de pommes de terre crues pour commencer. Certes, il est étrange de garder les pommes de terre cuites dans un sac, mais qui devons-nous en juger? : P
ajouté l'auteur user1491229, source
plus concrètement, quelqu'un vous remet une boîte fermée d'origine inconnue. Le sac dans la boîte contient-il des pommes de terre? Je ne suis intéressé que par 2 scénarios - A) il y a un sac dans la boîte qui contient des pommes de terre, ou B) il n'y a pas de pommes de terre et/ou pas de sac dans la boîte. Sémantiquement, oui, il y a une différence entre nul et vide, mais 99% du temps, cela m'est égal.
ajouté l'auteur dave thieben, source
Mais vide, il n'y a pas de pommes de terre dans le sac? False implique False ... (je ne suis pas en désaccord, mais une seconde perspective).
ajouté l'auteur Andy, source
Vous avez mangé cinq pommes de terre crues ?? Consulter immédiatement un médecin.
ajouté l'auteur Sierramike, source

Premièrement, il semble que ce code source lève ArgumentNullException , et non NullReferenceException .

Ceci dit, dans de nombreux cas, vous savez déjà que votre collection n'est pas nulle, car ce code est appelé uniquement à partir d'un code qui sait que la collection existe déjà. Vous n'aurez donc pas à y mettre très souvent la vérification de la valeur NULL. Mais si vous ne savez pas qu’il existe, il est judicieux de vérifier avant d’appeler Any() .

En tant que client de ce code, je me trompe, par exemple. ((int []) null) .Any() doit renvoyer false ?

Oui. La question à laquelle Any() répond est "cette collection contient-elle des éléments?" Si cette collection n'existe pas, alors la question elle-même est insensée; il ne peut ni contenir ni ne rien contenir, car il n'existe pas.

50
ajouté
@ChrisWohlert "sémantiquement, un ensemble non existant est un ensemble vide". C'est peut-être vrai en théorie des ensembles, mais nous ne traitons pas de la théorie des ensembles ici.
ajouté l'auteur JanC, source
null n'a pas de type @ChrisWohlert, sinon vous ne pourriez pas l'affecter à des objets de types incompatibles. null n'est pas un ensemble , une liste ou tout autre élément pouvant être vide; peu importe ce que T est.
ajouté l'auteur Matthew Read, source
@ChrisWohlert Si Add ne devrait pas créer une collection à partir de rien, pourquoi Any (puis vous dire qu'il est vide)? Cela me semble très incompatible.
ajouté l'auteur Andy, source
@ChrisWohlert "A est un ensemble vide. B est un ensemble contenant une pastèque. Est-ce que A est vide? Oui. Est-ce que B est vide? Non. Est-ce que C est vide? Uhhh ..."
ajouté l'auteur immibis, source
Je n'essaie pas de dire que null est un ensemble, mais que là où aucun ensemble n'existe, aucun élément n'existe.
ajouté l'auteur Chris Wohlert, source
Et ce n'est que l'équivalent de la question, y a-t-il des éléments?
ajouté l'auteur Chris Wohlert, source
Non, cela ne concerne que la question de savoir s'il y a des articles.
ajouté l'auteur Chris Wohlert, source
@ruakh uniquement lorsque T est un ensemble
ajouté l'auteur Chris Wohlert, source
Bien sûr, c'est comme cela que ça marche, et OP sait pertinemment que, mais sémantiquement, un ensemble non existant est un ensemble vide. Dans tous les cas, vous devez affirmer que cela n'a pas de sens sur le plan sémantique, pas comment cela fonctionne.
ajouté l'auteur Chris Wohlert, source
@ruakh Je ne sais pas ce qui constitue une personne vide , mais je peux imaginer un ensemble contenant des ensembles vides, oui. Comme dans {{}, {}, {}, {}} . Cependant, cela ne compte que lorsque T est une collection.
ajouté l'auteur Chris Wohlert, source
Bien sûr que nous ne le sommes pas, mais nous parlons de ce à quoi on peut s’attendre, et pour répondre à cela, nous devons faire appel à une théorie ou à une convention? Comme Telastyn l'a dit, c'est la convention C#, bien que si vous veniez d'ailleurs, vous pourriez ne pas vous attendre à cette convention. Cela ne me semble pas intuitif, qu'un ensemble manquant ne soit pas vide. J'aime la Ceci dit , je très dois rarement tester mes collections pour la valeur null.
ajouté l'auteur Chris Wohlert, source
Point pédant: En théorie des ensembles, un ensemble non existant n'est pas un ensemble vide. L'ensemble vide existe, et aucun ensemble non-existant ne l'est pas (et n'est en fait rien du tout, puisqu'il n'existe pas).
ajouté l'auteur buhtz, source
@ChrisWohlert: Évidemment, un ensemble peut contenir l'ensemble vide. mais vous semblez croire que {null} et {{}} devraient être équivalents. Je trouve cela fascinant. Je ne peux pas comprendre du tout.
ajouté l'auteur ndsurendra, source
@ChrisWohlert: Votre intuition m'intrigue. Pensez-vous également que sémantiquement, une personne inexistante est une personne vide, dont le nom est la chaîne vide et dont l'âge est égal à zéro et ainsi de suite? Et pensez-vous qu'un ensemble contenant null est équivalent à un ensemble contenant l'ensemble vide (et vice versa)?
ajouté l'auteur ndsurendra, source
.Add sur une collection null doit-il également créer une collection pour nous aussi?
ajouté l'auteur Matthew, source
@ChrisWohlert "sémantiquement, un ensemble non existant est un ensemble vide" - Non. Il n'y a pas de jeu inexistant. Le texte "un ensemble non existant" peut être utilisé avec d'autres mots pour dire des choses, comme s'il n'existait aucun ensemble contenant certaines propriétés, mais ni lui ni d'autres mots ne disent quoi que ce soit sur un ensemble inexistant, car aucun ensemble est inexistant.
ajouté l'auteur philipxy, source
@ChrisWohlert Si nous voulons obtenir tous les ensembles théoriques, alors (null) .Any() devrait également renvoyer true. Après tout, chaque collection que nous avons vérifiée contient au moins un élément. Alors maintenant, vous avez un appel qui renvoie à la fois vrai et faux.
ajouté l'auteur craciun iulian, source

Null signifie informations manquantes, pas aucun élément.

You might consider more broadly avoiding null — for example, use one of the built-in empty enumerables to represent a collection with no elements instead of null.

If you are returning null in some circumstances, you might change that to return the empty collection.  (Otherwise, if you're finding null's returned by library methods (not yours), that's unfortunate, and I would wrap them to normalize.)

See also https://stackoverflow.com/questions/1191919/what-does-linq-return-when-the-results-are-empty

23
ajouté
Ce null ne signifie pas que pas d'éléments consiste à utiliser une convention raisonnable. Il suffit de penser à OLESTRINGS en natif, là où cela signifie .
ajouté l'auteur Shizam, source
@DuDuplicator, parlez-vous de la liaison et de l'incorporation d'objets de Microsoft? Si vous vous souvenez bien, OLE date des années 90! De plus, de nombreux langages modernes (C #, Java, Swift) fournissent des fonctionnalités pour éliminer/éradiquer les utilisations de null.
ajouté l'auteur Ayyash, source
@ErikEidt Ils le font, en partie, parce que null ne signifie pas "informations manquantes". null n'a pas une seule interprétation significative. Au lieu de cela, il s'agit de savoir comment vous le traitez. null est parfois utilisé pour "des informations manquantes", mais également pour des "aucun élément", ainsi que pour "une erreur est survenue" ou "non initialisé" ou "en conflit" ou quelque chose d'arbitraire, ad hoc .
ajouté l'auteur ohias, source

Outre la syntaxe null-conditionnelle , il existe une autre technique permettant de résoudre ce problème: ne laissez jamais votre variable reste null .

Considérons une fonction qui accepte une collection en tant que paramètre. Si, aux fins de la fonction, null et vide sont équivalents, vous pouvez vous assurer qu'il ne contient jamais null au début:

public MyResult DoSomething(int a, IEnumerable words)
{
    words = words ?? Enumerable.Empty();

    if (!words.Any())
    {
        ...

Vous pouvez faire la même chose lorsque vous récupérez des collections à partir d'une autre méthode:

var words = GetWords() ?? Enumerable.Empty();

(Notez que dans les cas où vous avez le contrôle sur une fonction telle que GetWords et que null équivaut à la collection vide, il est préférable de renvoyer la collection vide à la première place. .)

Vous pouvez maintenant effectuer toute opération de votre choix sur la collection. Ceci est particulièrement utile si vous devez effectuer de nombreuses opérations qui échoueraient lorsque la collection est null , et dans les cas où vous obtiendrez le même résultat en bouclant ou en interrogeant un énumérateur vide, cela permettra d'éliminer < code> si est entièrement conditionnel.

13
ajouté

En tant que client de ce code, je me trompe, par exemple. ((int []) null) .Any() doit retourner false?

Oui, tout simplement parce que vous êtes en C# et que ce comportement est bien défini et documenté.

Si vous construisiez votre propre bibliothèque ou si vous utilisiez une langue différente avec une culture d'exception différente, il serait plus raisonnable de s'attendre à une valeur fausse.

Personnellement, j’ai l’impression que return false est une approche plus sûre qui rend votre programme plus robuste, mais au moins discutable.

12
ajouté
Imaginez que vous ayez un tableau rempli à partir d'un document JSON reçu via une requête Web et qu'en production, l'un des clients de votre API rencontre soudainement un problème: il envoie un fichier JSON partiellement non valide, ce qui vous oblige à désérialiser un tableau comme étant NULL. dans le pire des cas, le code a généré une exception de référence NULL au moment de l'entrée de la première demande incorrecte, ce que vous voyez dans la journalisation puis demandez immédiatement au client de corriger ses demandes rompues, OU votre code indique "oh, le tableau est vide, rien faire!" et silencieusement écrit «panier d'achat n'a pas d'articles» à la base de données pendant quelques semaines?
ajouté l'auteur fenway, source
Si quelque chose est brisé, qu'il s'agisse de mon code, d'un autre collègue ou d'un client, je préférerais que le code échoue immédiatement et que les gens le sachent qu'il continue dans un état potentiellement incohérent pendant une durée indéterminée. Pouvoir faire cela est bien sûr parfois un luxe et pas toujours approprié mais c'est l'approche que je préfère.
ajouté l'auteur fenway, source
Cette "robustesse" est souvent une illusion: si le code qui génère le tableau commence soudainement à générer des valeurs NULL en raison d'un bogue ou d'un autre problème, votre code "robuste" masque le problème. C'est un comportement approprié parfois, mais ce n'est pas un comportement sûr.
ajouté l'auteur fenway, source
@GoatInTheMachine - Je suis d'accord pour beaucoup de choses, mais les collections ont tendance à être différentes. Traiter null comme une collection vide n'est pas un comportement terriblement incohérent ou surprenant.
ajouté l'auteur Telastyn, source
@GoatInTheMachine - Je ne suis pas d'accord sur le fait qu'il s'agisse d'un paramètre par défaut sûr, mais vous avez raison de dire que le problème est masqué et qu'un tel comportement est parfois indésirable. D'après mon expérience, il est préférable de cacher le problème (ou de faire confiance aux tests unitaires pour détecter les problèmes d'autres codes) plutôt que de bloquer votre application en lançant des exceptions inattendues. Je veux dire vraiment - si le code d'appel est suffisamment cassé pour envoyer des nulls, quelle est la chance qu'ils gèrent les exceptions correctement?
ajouté l'auteur Telastyn, source

Si les contrôles nuls répétés vous importunent, vous pouvez créer votre propre méthode d'extension 'IsNullOrEmpty ()', afin de mettre en miroir la méthode String avec ce nom et de regrouper les appels null-check et .Any() dans un seul appel.

Sinon, la solution, mentionnée par @ 17 sur 26 dans un commentaire sous votre question, est également plus courte que la méthode "standard" et est raisonnablement claire pour les personnes familiarisées avec la nouvelle syntaxe null-conditionnelle.

if(myCollection?.Any() == true)
10
ajouté
@ jpmc26 ?? false est plus difficile à lire que myCollection? .Any() == true. Indépendamment de votre réflexe nauséeux, == true est ce que vous essayez de tester implicitement. ?? false ajoute simplement du bruit supplémentaire et vous devez toujours évaluer dans votre tête ce qui se passe si null, ce null == true échouera, presque sans que le lecteur ait même à connaître le ?. .
ajouté l'auteur Guillem Vicens, source
Accepter d'être en désaccord alors.
ajouté l'auteur Guillem Vicens, source
Vous pouvez également utiliser ?? false au lieu de == true . Cela me semblerait plus explicite (plus propre) car il ne traite que le cas de null , et ce n'est pas vraiment plus verbeux. De plus, == vérifie si des booléens déclenchent généralement mon réflexe nauséeux. =)
ajouté l'auteur jmibanez, source
@RyanTheLeach Je dois réfléchir plus avant pour savoir si == fonctionnera réellement. Je sais déjà ce qui se passe avec un booléen intuitivement quand il ne peut s'agir que de true ou false ; Je n'ai même pas à y penser. Mais ajoutons null dans le mélange, et je dois maintenant déterminer si ce == est correct. ?? élimine simplement le cas null , le ramenant à true et à false , que vous comprenez déjà immédiatement . En ce qui concerne mon réflexe nauséeux, lorsque je le vois pour la première fois, mon instinct immédiat est de simplement le supprimer car il est généralement inutile, ce que vous ne voulez pas voir se produire.
ajouté l'auteur jmibanez, source
@StevenRands: Excellent, merci.
ajouté l'auteur Kent, source
@ jpmc26: Je ne connais pas grand chose en C #. Pourquoi myCollection? .Any() ne suffit pas?
ajouté l'auteur Kent, source
J'ai écrit du code réel dans les deux sens, (x? .Any() == true) et (x? .Any() ?? false). Quand je reviendrai plus tard pour lire le code, ils seront plus difficiles à lire que ne le serait (x.Any ()), mais le ?? false me fait toujours arrêter de calculer ce qui est réellement l'intention. J'utilise ?? partout sans problème, mais pour une raison quelconque, le combiner avec un bool le rend vraiment difficile. Cela devient vraiment compliqué lorsque vous vérifiez! X.Any() au lieu de x.Any (), vous vous retrouvez avec quelque chose comme! (X?. Any() ?? false) ou (x?. Any() ! = faux). Aucun de ceux qui sont clairs en un coup d'œil ...
ajouté l'auteur checksum, source
@EricDuminil En raison du fonctionnement de l'opérateur null-conditionnel, myCollection? .Any() renvoie effectivement un booléen nullable plutôt qu'un booléen ordinaire (c'est-à-dire bool? Au lieu de bool). Il n'y a pas de conversion implicite de bool? bool, et c'est un bool dont nous avons besoin dans cet exemple particulier (à tester dans le cadre de la condition si ). Par conséquent, nous devons soit comparer explicitement avec true , soit utiliser l’opérateur null-coalescing (??).
ajouté l'auteur Steven Rands, source

En tant que client de ce code, je me trompe, par exemple. ((int []) null) .Any() doit retourner false?

Si vous vous interrogez sur les attentes, vous devez penser aux intentions.

null means something very different from Enumerable.Empty

Comme indiqué dans la réponse d'Erik Eidt , il existe une différence de sens entre null et un collection vide.

Voyons d'abord comment ils sont censés être utilisés.

Le livre Directives de conception de structure: conventions, idiomes et modèles pour les bibliothèques .NET réutilisables, 2e édition rédigées par les architectes Microsoft Krzysztof Cwalina et Brad Abrams indique la meilleure pratique suivante:

X NE RENVOIE PAS de valeurs NULL à partir de propriétés de collection ou de méthodes renvoyant des collections. Retourne une collection vide ou un tableau vide à la place.

Consider your calling a method that is ultimately getting data from a database: If you receive an empty array or Enumerable.Empty this simply means your sample space is empty, i.e. your result is an empty set. Receiving null in this context, however, would signify an error state.

Dans la même ligne de pensée que pour la analogie de Dan Wilson avec la pomme de terre , il est logique de poser des questions sur vos données même si est un ensemble vide . Mais cela a beaucoup moins de sens , si il n'y a pas de jeu .

8
ajouté
@ jpmc26: Je pense que le but de cette réponse est de dire qu'en codant les instructions pour C#, elles ont un sens différent. Vous pouvez toujours corriger le code, où null et vide sont identiques si vous le souhaitez. Et dans certains cas, vous pouvez également faire la même chose, qu’elle soit nulle ou vide, mais cela ne signifie pas qu’ils signifient la même chose.
ajouté l'auteur user13762, source
Je suis peut-être déconcerté par ce que vous dites, mais lorsque null et empty sont équivalents pour générer un résultat, je ne vois pas pourquoi vous n'utiliseriez pas simplement des éléments séparés, c'est null et des contrôles vides plutôt que de vouloir une méthode qui fait les deux à la fois mais ne vous permet pas de les distinguer dans d'autres situations ...
ajouté l'auteur user13762, source
Leur sens différent dépend fortement du contexte. Souvent, pour les besoins de la logique, ils représentent des concepts équivalents. Pas toujours , mais souvent.
ajouté l'auteur jmibanez, source
@Chris Et je dis que les directives de codage des personnes qui ont conçu le langage ne peuvent pas remplacer l'évaluation de votre code dans le contexte de vos besoins, des bibliothèques que vous utilisez et des autres développeurs avec lesquels vous travaillez. L'idée selon laquelle ils ne seront jamais équivalents dans le contexte est un idéalisme pie-dans-le-ciel. Elles pourraient même ne pas être équivalentes pour les données disponibles en général, mais être équivalentes pour générer un résultat dans une fonction spécifique que vous écrivez; dans ce cas, vous avez besoin de prendre en charge les deux mais assurez-vous qu'ils génèrent le même résultat.
ajouté l'auteur jmibanez, source
@Chris Je parle de contextes différents (qui correspondent souvent à des portées). Considérons une fonction par exemple. null et vide peuvent entraîner la même valeur de retour pour la fonction, mais cela ne signifie pas que null et vide signifient exactement la même chose dans la portée de l'appel.
ajouté l'auteur jmibanez, source

Il existe de nombreuses réponses expliquant pourquoi null et les éléments vides sont différents et qu'il existe suffisamment d'opinions pour expliquer à la fois pourquoi elles devraient être traitées différemment ou non. Cependant, vous demandez:

En tant que client de ce code, je me trompe, par exemple. ((int []) null) .Any() doit retourner false?

C'est une attente parfaitement raisonnable . Vous êtes aussi juste que quelqu'un qui préconise le comportement actuel. Je suis d'accord avec la philosophie de mise en œuvre actuelle, mais les facteurs déterminants ne sont pas - uniquement - fondés sur des considérations hors contexte.

Given that Any() without predicate is essentially Count() > 0 then what do you expect from this snippet?

List list = null;
if (list.Count > 0) {}

Ou un générique:

List list = null;
if (list.Foo()) {}

Je suppose que vous attendez NullReferenceException .

  • Any() is an extension method, it should smoothly integrate with the extended object then throwing an exception is the least surprising thing.
  • Not every .NET language supports extension methods.
  • You can always call Enumerable.Any(null) and there you definitely expect ArgumentNullException. It's the same method and it has to be consistent with - possibly - almost EVERYTHING else in the Framework.
  • Accessing a null object is a programming error, framework should not enforce null as magic value. If you use it that way then it's your responsibility to deal with it.
  • It's opinionated, you think one way and I think another way. Framework should be as much unopinionated as possible.
  • If you have a special case then you must be consistent: you have to take more and more highly opinionated decisions about all the other extension methods: if Count() seems an easy decision then Where() is not. What about Max()? It throws an exception for an EMPTY list, shouldn't it throw also for a null one?

Ce que les concepteurs de bibliothèque faisaient avant LINQ était d’introduire des méthodes explicites lorsque null était une valeur valide (par exemple, String.IsNullOrEmpty() ), puis ils HAD être cohérent avec la philosophie de conception existante . Cela dit, même s’il est assez simple d’écrire, deux méthodes EmptyIfNull() et EmptyOrNull() peuvent être utiles.

3
ajouté

Jim est censé laisser des pommes de terre dans chaque sac. Sinon, je vais le tuer.

Jim a un sac contenant cinq pommes de terre. Y at-il des pommes de terre .Any() dans le sac?

"Yes," you say. <= true

Ok, Jim vit cette fois-ci.

Jim prend toutes les pommes de terre et les mange. Existe-t-il des pommes de terre dans le sac?

"No," you say. <= false

Il est temps de tuer Jim.

Jim incinère complètement le sac dans un feu. Existe-t-il des pommes de terre dans le sac?

"There is no bag." <= ArgumentNullException

Should Jim live or die? Well we didn't expect this so I need a ruling. Is letting Jim get away with this a bug or not?

Vous pouvez utiliser des annotations pour signaler que vous ne supportez pas les shenanigans nuls de cette façon.

public bool Any( [NotNull] List bag ) 

Mais votre chaîne d’outils doit le supporter. Ce qui signifie que vous finirez probablement par écrire des chèques.

1
ajouté

Je pense que l’essentiel ici est qu’en renvoyant la valeur false au lieu de lever une exception, vous masquerez les informations pouvant présenter un intérêt pour les futurs lecteurs/modificateurs de votre code.

S'il est possible que la liste soit nulle, je suggérerais d'avoir un chemin logique séparé pour cela, car sinon, à l'avenir, quelqu'un pourrait ajouter une logique basée sur une liste (comme un ajout) à else {} de votre if exception non désirée qu’ils n’ont aucune raison de prédire.

La lisibilité et la maintenabilité sont des atouts majeurs: «Je dois écrire une condition supplémentaire» à chaque fois.

0
ajouté

Si cela vous dérange tellement, je suggère une méthode d'extension simple.

static public IEnumerable NullToEmpty(this IEnumerable source)
{
    return (source == null) ? Enumerable.Empty() : source;
}

Maintenant vous pouvez faire ceci:

List list = null;
var flag = list.NullToEmpty().Any( s => s == "Foo" );

... et flag sera défini sur false .

0
ajouté
Cela ne répond pas à la question posée.
ajouté l'auteur Not Joe Bloggs, source
@ KennethK. Mais cela semble résoudre le problème présenté.
ajouté l'auteur NirIzr, source
Plus naturel d'écrire l'instruction return comme suit: return source ?? Enumerable.Empty ();
ajouté l'auteur Amida, source

En règle générale, j'écris l'essentiel de mon code pour supposer que l'appelant est responsable de ne pas me transmettre de données nulles, principalement pour les raisons décrites dans Dites non à Null (et autres messages similaires). Le concept de null est souvent mal compris, et pour cette raison, il est conseillé d’initialiser vos variables à l’avance, autant que possible. En supposant que vous travaillez avec une API raisonnable, vous devriez idéalement ne jamais récupérer une valeur null, vous ne devriez donc jamais avoir à vérifier la valeur null. Le point nul, comme d'autres réponses l'ont indiqué, est de vous assurer que vous disposez d'un objet valide (un "sac", par exemple) avant de continuer. Ce n'est pas une fonctionnalité de Any , mais une fonctionnalité du langage lui-même. Vous ne pouvez pas effectuer la plupart des opérations sur un objet null, car il n'existe pas. C'est comme si je vous demandais de conduire au magasin avec ma voiture, sauf que je ne possède pas de voiture, vous n'avez donc aucun moyen d'utiliser ma voiture pour aller au magasin. Il est absurde d'exécuter une opération sur quelque chose qui n'existe pas littéralement.

Il existe des cas pratiques d'utilisation de null, par exemple lorsque vous ne disposez littéralement pas des informations demandées (par exemple, si je vous ai demandé quel est mon âge, le choix le plus probable pour vous de répondre maintenant est "Je ne sais pas ", qui est ce qu’une valeur nulle est). Cependant, dans la plupart des cas, vous devriez au moins savoir si vos variables sont initialisées ou non. Et si vous ne le faites pas, alors je vous recommanderais de resserrer votre code. La première chose que je fais dans un programme ou une fonction est d’initialiser une valeur avant de l’utiliser. Il est rare que je doive vérifier les valeurs NULL, car je peux garantir que mes variables ne sont pas nulles. C'est une bonne habitude à prendre, et plus vous vous rappelez d'initialiser vos variables fréquemment, moins vous devez vérifier la valeur null.

0
ajouté

C’est une question sur les méthodes d’extension de C# et leur philosophie de conception. Je pense donc que le meilleur moyen de répondre à cette question est de citer Documentation de MSDN sur les méthodes d'extension :

Les méthodes d'extension vous permettent "d'ajouter" des méthodes à des types existants sans créer de nouveau type dérivé, ni recompiler, ni modifier le type d'origine. Les méthodes d'extension sont un type spécial de méthode statique, mais elles sont appelées comme s'il s'agissait de méthodes d'instance sur le type étendu. Pour le code client écrit en C#, F # et Visual Basic, il n’existe aucune différence apparente entre l’appel d’une méthode d’extension et les méthodes réellement définies dans un type.

     

     

En général, nous vous recommandons d'implémenter les méthodes d'extension avec parcimonie et uniquement lorsque vous devez le faire. Dans la mesure du possible, le code client qui doit étendre un type existant doit le faire en créant un nouveau type dérivé du type existant. Pour plus d'informations, voir Héritage.

     

Lorsque vous utilisez une méthode d'extension pour étendre un type dont le code source ne peut pas être modifié, vous courez le risque qu'une modification de l'implémentation du type entraîne la rupture de votre méthode d'extension.

     

Si vous implémentez des méthodes d'extension pour un type donné, rappelez-vous les points suivants:

     
      
  • Une méthode d'extension ne sera jamais appelée si elle a la même signature qu'une méthode définie dans le type.
  •   
  • Les méthodes d'extension sont incluses dans la portée au niveau de l'espace de noms. Par exemple, si vous avez plusieurs classes statiques contenant des méthodes d'extension dans un seul espace-noms nommé Extensions , elles seront toutes portées à la portée de la directive à l'aide de la directive Extensions; .
  •   

Pour résumer, les méthodes d'extension sont conçues pour ajouter des méthodes d'instance à un type particulier, même lorsque les développeurs ne peuvent pas le faire directement. Et comme les méthodes d'instance remplaceront toujours les méthodes d'extension si elles sont présentes (si elles sont appelées à l'aide de la syntaxe de méthode d'instance), cela devrait seulement si vous ne pouvez pas ajouter directement de méthode ou étendre la classe. . *

En d’autres termes, une méthode d’extension doit agir comme une méthode d’instance, car elle peut être transformée en méthode d’instance par un client. Et comme une méthode d'instance doit lancer si l'objet sur lequel il est appelé est null , il en va de même pour la méthode d'extension.


*As a side note, this is exactly the situation that the designers of LINQ faced: when C# 3.0 was released, there were already millions of clients that were using System.Collections.IEnumerable and System.Collections.Generic.IEnumerable, both in their collections and in foreach loops. These classes returned IEnumerator objects which only had the two methods Current and MoveNext, so adding any additional required instance methods, such as Count, Any, etc., would be breaking these millions of clients. So, in order to provide this functionality (especially since it can be implemented in terms of Current and MoveNext with relative ease), they released it as extension methods, which can be applied to any currently existing IEnumerable instance and can also be implemented by classes in more efficient ways. Had C#'s designers decided to release LINQ on day one, it would have been provided as instance methods of IEnumerable, and they probably would have designed some kind of system to provide default interface implementations of those methods.

0
ajouté