Renvoie une valeur magique, déclenche une exception ou renvoie false en cas d'échec?

83

Je finis parfois par écrire une méthode ou une propriété pour une bibliothèque de classes pour laquelle il n'est pas exceptionnel de ne pas avoir de vraie réponse, mais un échec. Quelque chose ne peut pas être déterminé, n'est pas disponible, n'a pas été trouvé, n'est pas possible actuellement ou il n'y a plus de données disponibles.

Je pense qu'il existe trois solutions possibles pour une situation aussi peu exceptionnelle pour indiquer un échec en C # 4:

  • renvoyer une valeur magique qui n'a pas d'autre sens (comme nullet -1);
  • lancer une exception (par exemple KeyNotFoundException);
  • return falseet fournit la valeur de retour actuelle dans un outparamètre (tel que Dictionary<,>.TryGetValue).

Les questions sont donc les suivantes: dans quelle situation non exceptionnelle devrais-je lancer une exception? Et si je ne devais pas jeter: quand est-ce que le retour d’une valeur magique indiquée plus haut implémente-t-il une Try*méthode avec un outparamètre ? (Pour moi, le outparamètre semble sale et il est plus fastidieux de l'utiliser correctement.)

Je cherche des réponses factuelles, telles que des réponses impliquant des directives de conception (je ne connais aucune Try*méthode), une convivialité (comme je le demande pour une bibliothèque de classe), une cohérence avec la BCL et une lisibilité.


Dans la bibliothèque de classes .NET Framework Base, les trois méthodes sont utilisées:

Notez que, comme il a Hashtableété créé à l’époque où il n’existait pas de génériques en C #, il utilise objectet peut donc être renvoyé nullcomme une valeur magique. Mais avec les génériques, des exceptions sont utilisées Dictionary<,>et, au départ, ce n’était pas le cas TryGetValue. Apparemment, les idées changent.

De toute évidence, la Item- TryGetValueet Parse- TryParsedualité existe pour une raison. Je suppose donc que le lancement d’exceptions pour les échecs non exceptionnels n’est pas effectué en C # 4 . Cependant, les Try*méthodes n'existaient pas toujours, même lorsqu'elles Dictionary<,>.Itemexistaient.

Daniel AA Pelsmaeker
la source
6
"les exceptions signifient des bugs". demander un dictionnaire pour un élément inexistant est un bug; demander à un flux de lire des données lorsqu'il s'agit d'un EOF se produit chaque fois que vous utilisez un flux. (Ceci résume la longue réponse joliment formatée que je n'ai pas eu l'occasion de soumettre :))
KutuluMike
6
Ce n'est pas que je pense que votre question est trop large, c'est que ce n'est pas une question constructive. Il n'y a pas de réponse canonique car les réponses sont déjà visibles.
George Stocker
2
@ GeorgeStocker Si la réponse était simple et évidente, je n'aurais pas posé la question. Le fait que les gens puissent expliquer pourquoi un choix donné est préférable, quel que soit son point de vue (performances, lisibilité, utilisabilité, maintenabilité, consignes de conception ou cohérence) le rend fondamentalement non canonique mais répond à ma satisfaction. Toutes les questions peuvent recevoir une réponse quelque peu subjective. Apparemment, vous vous attendez à ce qu'une question ait essentiellement des réponses similaires, ce qui en fait une bonne question.
Daniel AA Pelsmaeker
3
@Virtlink: George est un modérateur élu par la communauté qui consacre beaucoup de son temps à aider à maintenir Stack Overflow. Il a expliqué pourquoi il a fermé la question et la FAQ le conforte.
Eric J.
15
La question appartient ici, non pas parce que c'est subjectif, mais parce que c'est conceptuel. Règle générale: si vous êtes assis devant votre code IDE, posez-le à Stack Overflow. Si vous vous trouvez devant un brainstorming sur un tableau blanc, posez-le ici aux programmeurs.
Robert Harvey

Réponses:

56

Je ne pense pas que vos exemples soient vraiment équivalents. Il existe trois groupes distincts, chacun ayant sa propre raison de se comporter.

  1. La valeur magique est une bonne option lorsqu'il existe une condition "jusqu'à ce que" telle que StreamReader.Readou lorsqu'il existe une valeur simple à utiliser qui ne sera jamais une réponse valide (-1 pour IndexOf).
  2. Lance une exception lorsque la sémantique de la fonction indique que l'appelant est certain que cela fonctionnera. Dans ce cas, une clé inexistante ou un double format incorrect est vraiment exceptionnel.
  3. Utilisez un paramètre out et retournez un bool si la sémantique cherche à savoir si l'opération est possible ou non.

Les exemples que vous fournissez sont parfaitement clairs pour les cas 2 et 3. Pour les valeurs magiques, on peut dire s’il s’agit d’une bonne décision de conception ou non dans tous les cas.

Le NaNretour par Math.Sqrtest un cas particulier - il suit la norme de virgule flottante.

Anders Abel
la source
10
En désaccord avec le chiffre 1. Les valeurs magiques ne sont jamais une bonne option car elles nécessitent que le codeur suivant connaisse la signification de la valeur magique. Ils ont également nui à la lisibilité. Je ne peux penser à aucun cas dans lequel l'utilisation d'une valeur magique est meilleure que le modèle Try.
0b101010
1
Mais qu'en est-il de la Either<TLeft, TRight>monade?
Sara
2
@ 0b101010: je viens de passer un peu de temps à regarder comment streamreader.read peut revenir en toute sécurité -1 ...
jmoreno
33

Vous essayez de communiquer à l'utilisateur de l'API ce qu'il doit faire. Si vous lancez une exception, rien ne l'oblige à l'attraper et la seule lecture de la documentation leur permettra de connaître toutes les possibilités. Personnellement, je trouve qu'il est lent et fastidieux de fouiller dans la documentation pour trouver toutes les exceptions qu'une méthode peut générer (même si c'est dans intellisense, je dois encore les copier manuellement).

Une valeur magique nécessite toujours que vous lisiez la documentation et, éventuellement, référenciez une consttable pour décoder la valeur. Au moins, il n’ya pas de surcharge d’une exception pour ce que vous appelez un événement non exceptionnel.

C'est pourquoi, même si les outparamètres sont parfois mal vus, je préfère cette méthode, avec la Try...syntaxe. C'est la syntaxe canonique .NET et C #. Vous indiquez à l'utilisateur de l'API qu'il doit vérifier la valeur de retour avant d'utiliser le résultat. Vous pouvez également inclure un deuxième outparamètre avec un message d'erreur utile, ce qui facilite encore le débogage. C'est pourquoi je vote pour la solution Try...avec outparamètre.

Une autre option consiste à retourner un objet "résultat" spécial, bien que cela me paraisse beaucoup plus fastidieux:

interface IMyResult
{
    bool Success { get; }
    // Only access this if Success is true
    MyOtherClass Result { get; }
    // Only access this if Success is false
    string ErrorMessage { get; }
}

Ensuite, votre fonction a l’ air juste car elle n’a que des paramètres d’entrée et ne renvoie qu’une chose. C'est juste que la seule chose qu'il retourne est une sorte de tuple.

En fait, si vous aimez ce genre de choses, vous pouvez utiliser les nouvelles Tuple<>classes introduites dans .NET 4. Personnellement, je n'aime pas le fait que la signification de chaque champ soit moins explicite, car je ne peux pas donner Item1et Item2noms utiles.

Scott Whitlock
la source
3
Personnellement, je l' utilise souvent un conteneur de résultat comme votre IMyResultcar il est possible de communiquer un résultat plus complexe que juste trueou falseou la valeur du résultat. Try*()n'est utile que pour des choses simples comme la conversion de chaîne en entier.
this.myself
1
Bon post. Pour moi, je préfère l'idiome de la structure de résultat que vous avez décrit ci-dessus, par opposition à la séparation entre les valeurs "return" et les paramètres "out". Le garde propre et bien rangé.
Ocean Airdrop
2
Le problème avec le paramètre de sortie est que lorsque vous avez 50 fonctions dans un programme complexe, comment communiquez-vous ce message d'erreur à l'utilisateur? Lancer une exception est beaucoup plus simple que d'avoir des couches d'erreurs de vérification. Quand vous en avez un, lancez-le et peu importe la profondeur.
lance
@rolls - Lorsque nous utilisons des objets de sortie, nous supposons que l'appelant immédiat fera ce qu'il veut avec la sortie: traitez-le localement, ignorez-le ou faites une bulle en lançant cela encapsulé dans une exception. C'est le meilleur des deux mots - l'appelant peut voir clairement toutes les sorties possibles (avec des énumérations, etc.), peut décider de la suite à donner à l'erreur et n'a pas besoin d'essayer d'intercepter chaque appel. Si vous vous attendez surtout à gérer le résultat immédiatement ou à l'ignorer, les objets de retour sont plus faciles. Si vous voulez envoyer toutes les erreurs aux couches supérieures, les exceptions sont plus faciles.
drizin
2
Ceci réinvente simplement les exceptions vérifiées en C # de manière beaucoup plus fastidieuse que les exceptions vérifiées en Java.
Hiver
17

Comme vos exemples le montrent déjà, chaque cas de ce type doit être évalué séparément et il existe un large spectre de gris entre les "circonstances exceptionnelles" et le "contrôle de flux", en particulier si votre méthode est censée être réutilisable et peut être utilisée de manières très différentes. qu'il a été conçu à l'origine pour. Ne vous attendez pas à ce que tous ici s'entendent sur ce que signifie "non exceptionnel", surtout si vous discutez immédiatement de la possibilité d'utiliser des "exceptions" pour mettre cela en œuvre.

Nous pourrions également ne pas être d’accord sur la conception qui rend le code le plus facile à lire et à maintenir, mais je suppose que le concepteur de la bibliothèque en a une vision personnelle claire et qu’il ne lui faut que l’équilibrer par rapport aux autres considérations en jeu.

Réponse courte

Suivez vos instincts sauf lorsque vous concevez des méthodes assez rapides et attendez-vous à une possibilité de réutilisation imprévue.

Longue réponse

Chaque futur appelant peut librement faire la traduction entre les codes d'erreur et les exceptions comme il le souhaite dans les deux sens; Cela rend les deux approches de conception presque équivalentes, à l'exception des performances, de la convivialité du débogueur et de certains contextes d'interopérabilité restreinte. Cela se résume généralement à la performance, alors concentrons-nous là-dessus.

  • En règle générale, attendez-vous à ce qu'une exception soit générée 200 fois plus lentement qu'un retour régulier (en réalité, il y a un écart important).

  • En règle générale, le lancement d'une exception peut souvent permettre un code beaucoup plus propre comparé à la plus grossière des valeurs magiques, car vous ne comptez pas sur le programmeur qui traduit le code d'erreur en un autre code d'erreur, car il parcourt plusieurs couches de code client vers un point où il y a suffisamment de contexte pour le gérer de manière cohérente et adéquate. (Cas particulier: a nulltendance à être meilleur ici que d’autres valeurs magiques en raison de sa tendance à se traduire automatiquement NullReferenceExceptionen cas de défauts, mais pas tous; types de défauts; généralement, mais pas toujours, assez proches de la source du défaut. )

Alors, quelle est la leçon?

Pour une fonction appelée quelques fois au cours de la durée de vie d'une application (comme son initialisation), utilisez tout ce qui vous donne un code plus propre et plus facile à comprendre. La performance ne peut pas être une préoccupation.

Pour une fonction jetable, utilisez tout ce qui vous donne un code plus propre. Effectuez ensuite un profilage (si nécessaire) et modifiez les exceptions en codes de retour s’ils font partie des goulets d’étranglement présumés fondés sur des mesures ou la structure globale du programme.

Pour une fonction réutilisable coûteuse, utilisez tout ce qui vous donne un code plus propre. Si, fondamentalement, vous devez toujours subir un aller-retour sur le réseau ou analyser un fichier XML sur disque, le temps système nécessaire au lancement d'une exception est probablement négligeable. Il est plus important de ne pas perdre les détails de toute défaillance, même accidentellement, que de revenir très rapidement d'une "défaillance non exceptionnelle".

Une fonction maigre réutilisable nécessite plus de réflexion. En utilisant des exceptions, vous forcez quelque chose comme un ralentissement de 100 fois sur les appelants qui verront l'exception sur la moitié de leurs (nombreux) appels, si le corps de la fonction s'exécute très rapidement. Les exceptions constituent toujours une option de conception, mais vous devrez fournir une alternative peu onéreuse aux appelants qui ne peuvent se le permettre. Regardons un exemple.

Vous avez énuméré un excellent exemple de Dictionary<,>.Itemce qui, en gros, est passé du renvoi de nullvaleurs au jeté KeyNotFoundExceptionentre .NET 1.1 et .NET 2.0 (uniquement si vous êtes prêt à considérer Hashtable.Itemson précurseur non générique pratique). La raison de ce "changement" n’est pas sans intérêt ici. L’optimisation des performances des types de valeur (plus de boxe) a fait de la valeur magique initiale ( null) une non-option; outles paramètres ne rapportent qu’une petite partie des coûts de performance. Cette dernière considération de performance est totalement négligeable par rapport à la surcharge de lancer un KeyNotFoundException, mais la conception des exceptions est toujours supérieure ici. Pourquoi?

  • Les paramètres ref / out entraînent des coûts chaque fois, pas seulement dans le cas "d'échec"
  • Toute personne intéressée peut appeler Containsavant tout appel de l'indexeur, et ce modèle se lit de manière totalement naturelle. Si un développeur souhaite appeler mais oublie d'appeler Contains, aucun problème de performances ne peut s'y glisser; KeyNotFoundExceptionest assez fort pour être remarqué et corrigé.
Jirka Hanika
la source
Je pense que 200x est optimiste quant à la performance des exceptions ... voir blogs.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx "Performances et tendances" juste avant les commentaires.
gbjbaanb
@ gbjbaanb - Eh bien, peut-être. Cet article utilise un taux d'échec de 1% pour discuter du sujet, qui n'est pas un problème totalement différent. Mes propres pensées et mes mesures restées vaguement dans la mémoire proviendraient du contexte de C ++ implémenté sous forme de table (voir la section 5.4.1.2 du présent rapport) . Mais je vais faire une expérience avec .NET et éventuellement modifier cette valeur approximative.J'ai déjà insisté sur la variance.
Jirka Hanika Le
Le coût des paramètres ref / out est-il si élevé alors? Comment? Et appeler Containsavant un appel à un indexeur peut provoquer une condition de concurrence qui ne doit pas nécessairement être présente TryGetValue.
Daniel AA Pelsmaeker
@gbjbaanb - Expérimenté terminé. J'étais paresseux et utilisais mono sous Linux. Les exceptions m'ont donné environ 563 000 lancers en 3 secondes. Les retours m'ont donné ~ 10900000 retours en 3 secondes. C'est 1:20, même pas 1: 200. Je recommande toujours de penser à 1: 100+ pour un code plus réaliste. (La variante param, comme je l'avais prédit, avait un coût négligeable - je soupçonne en fait que la gigue avait probablement optimisé l'appel dans son exemple minimaliste s'il n'y avait pas eu d'exception, quelle que soit la signature.)
Jirka Hanika Le
@Virtlink - Accord sur la sécurité des threads en général, mais étant donné que vous avez fait référence à .NET 4, utilisez-le ConcurrentDictionarypour un accès multithread et Dictionarypour un accès à un seul thread pour des performances optimales. Autrement dit, ne pas utiliser `` Contains` ne rend PAS le thread de code sûr avec cette Dictionaryclasse particulière .
Jirka Hanika
10

Quelle est la meilleure chose à faire dans une situation aussi peu exceptionnelle pour indiquer un échec, et pourquoi?

Vous ne devriez pas permettre l'échec.

Je sais, c'est vague et idéaliste, mais écoute-moi. Lors de la conception, il existe un certain nombre de cas dans lesquels vous avez la possibilité de privilégier une version ne comportant aucun mode de défaillance. Au lieu d'avoir un 'FindAll' qui échoue, LINQ utilise une clause where qui renvoie simplement un énumérable vide. Au lieu d'avoir un objet qui doit être initialisé avant d'être utilisé, laissez le constructeur initialiser l'objet (ou initialiser lorsque le non-initialisé est détecté). La clé est la suppression de la branche de défaillance dans le code du consommateur. C'est le problème, alors concentrez-vous dessus.

Une autre stratégie pour cela est le KeyNotFoundscénario. Dans presque toutes les bases de code sur lesquelles j'ai travaillé depuis la version 3.0, il existe quelque chose de similaire à cette méthode d'extension:

public static class DictionaryExtensions {
    public static V GetValue<K, V>(this IDictionary<K, V> arg, K key, Func<K,V> ifNotFound) {
        if (!arg.ContainsKey(key)) {
            return ifNotFound(key);
        }

        return arg[key];
    }
}

Il n'y a pas de véritable mode de défaillance pour cela. ConcurrentDictionarya un similaire GetOrAddconstruit en.

Cela dit, il y aura toujours des moments où c'est tout simplement inévitable. Tous trois ont leur place, mais je privilégierais la première option. En dépit de tout ce qui est fait du danger de null, il est bien connu et s'inscrit dans bon nombre des scénarios «élément introuvable» ou «résultat non applicable» qui composent l'ensemble «défaillance non exceptionnelle». En particulier lorsque vous créez des types de valeur nullable, la signification de «cela peut échouer» est très explicite dans le code et difficile à oublier / à gâcher.

La deuxième option est suffisante lorsque votre utilisateur fait quelque chose de stupide. Vous donne une chaîne avec un format incorrect, essaie de fixer la date au 42 décembre ... quelque chose que vous voulez que les choses explosent rapidement et de façon spectaculaire lors des tests afin que le mauvais code soit identifié et corrigé.

La dernière option est celle que je déteste de plus en plus. Les paramètres Out sont délicats et tendent à violer certaines des meilleures pratiques lors de la création de méthodes, comme se concentrer sur une chose et ne pas avoir d’effets secondaires. De plus, le paramètre out n’est généralement significatif que lorsqu’il réussit. Cela dit, elles sont essentielles pour certaines opérations qui sont généralement limitées par des problèmes de simultanéité ou des problèmes de performances (pour lesquels vous ne souhaitez pas effectuer un deuxième voyage dans la base de données, par exemple).

Si la valeur de retour et le paramètre out ne sont pas triviaux, la suggestion de Scott Whitlock concernant un objet de résultat est préférée (comme la Matchclasse de Regex ).

Telastyn
la source
7
Bête noire ici: les outparamètres sont complètement orthogonaux à la question des effets secondaires. La modification d'un refparamètre est un effet secondaire et la modification de l'état d'un objet que vous transmettez via un paramètre d'entrée est un effet secondaire, mais un outparamètre n'est qu'un moyen peu pratique de faire en sorte qu'une fonction renvoie plus d'une valeur. Il n'y a pas d'effet secondaire, juste plusieurs valeurs de retour.
Scott Whitlock
J'ai dit avoir tendance à , à cause de la façon dont les gens les utilisent. Comme vous le dites, ce sont simplement des valeurs de retour multiples.
Telastyn
Mais si vous n'aimez pas les paramètres et utilisez des exceptions pour faire exploser les choses de façon spectaculaire lorsque le format est incorrect ... comment gérez-vous alors le cas où votre format est une entrée utilisateur? Ensuite, un utilisateur peut faire sauter des objets ou encourt la peine de performance de lancer puis de capturer une exception. Droite?
Daniel AA Pelsmaeker
@virtlink en ayant une méthode de validation distincte. De toute façon, vous en avez besoin pour envoyer des messages appropriés à l'interface utilisateur avant qu'elle ne l'envoie.
Telastyn
1
Il existe un modèle légitime pour les paramètres out, et cette fonction comporte des surcharges qui renvoient des types différents. La résolution de surcharge ne fonctionnera pas pour les types de retour, mais elle le sera pour les paramètres externes.
Robert Harvey
2

Toujours préférer lancer une exception. Il possède une interface uniforme entre toutes les fonctions susceptibles d’échouer, et indique les échecs aussi bruyamment que possible - une propriété très souhaitable.

Notez que Parseet TryParsene sont pas vraiment la même chose en dehors des modes de défaillance. Le fait que TryParsela valeur puisse également être renvoyée est en quelque sorte orthogonal. Prenons le cas où, par exemple, vous validez une entrée. Vous ne vous souciez pas réellement de la valeur, tant qu'elle est valide. Et il n'y a rien de mal à offrir une sorte de IsValidFor(arguments)fonction. Mais il ne peut jamais être le principal mode de fonctionnement.

DeadMG
la source
4
Si vous traitez un grand nombre d'appels à une méthode, les exceptions peuvent avoir un impact négatif considérable sur les performances. Les exceptions devraient être réservées à des conditions exceptionnelles et seraient parfaitement acceptables pour la validation de la saisie de formulaire, mais pas pour l'analyse de nombres de fichiers volumineux.
Robert Harvey
1
C'est un besoin plus spécialisé, pas le cas général.
DeadMG
2
Donc tu dis. Mais vous avez utilisé le mot "toujours". :)
Robert Harvey
@DeadMG, d'accord avec RobertHarvey bien que je pense que la réponse a été rejetée de manière exagérée, si elle était modifiée pour refléter "la plupart du temps" et indiquer ensuite des exceptions (sans jeu de mots) au cas général où il s'agit de hautes performances fréquemment utilisées appels à envisager d'autres options.
Gerald Davis
Les exceptions ne sont pas chères. Il peut être coûteux de capturer des exceptions en profondeur, car le système doit dérouler la pile au point critique le plus proche. Mais ce "cher" est relativement petit et ne devrait pas être craint, même dans les boucles imbriquées.
Matthew Whited le
2

Comme d'autres l'ont fait remarquer, la valeur magique (y compris une valeur de retour booléenne) n'est pas une excellente solution, sauf en tant que marqueur de "fin de plage". Raison: la sémantique n'est pas explicite, même si vous examinez les méthodes de l'objet. Vous devez en fait lire la documentation complète de l'objet entier jusqu'à "oh ouais, s'il renvoie -42, cela signifie bla bla bla".

Cette solution peut être utilisée pour des raisons historiques ou pour des raisons de performances, mais doit sinon être évitée.

Cela laisse deux cas généraux: probing ou exceptions.

Ici, la règle de base est que le programme ne doit pas réagir aux exceptions, sauf pour gérer lorsque le programme / involontairement / viole une condition. Le sondage devrait être utilisé pour s'assurer que cela n'arrive pas. Par conséquent, une exception signifie soit que le sondage pertinent n’a pas été effectué à l’avance, soit que quelque chose de tout à fait inattendu s’est passé.

Exemple:

Vous voulez créer un fichier à partir d'un chemin donné.

Vous devez utiliser l'objet File pour déterminer à l'avance si ce chemin est légal pour la création ou l'écriture de fichier.

Si votre programme finit toujours par essayer d'écrire sur un chemin illégal ou illisible, vous devriez obtenir une excaption. Cela peut se produire en raison d'une situation de concurrence critique (un autre utilisateur a supprimé le répertoire ou l'a rendu en lecture seule après avoir rencontré des problèmes).

La tâche consistant à gérer une défaillance inattendue (signalée par une exception) et à vérifier si les conditions sont correctes pour l'opération à l'avance (vérification) sera généralement structurée différemment et devrait donc utiliser des mécanismes différents.

Anders Johansen
la source
0

Je pense que le Trymodèle est le meilleur choix, quand le code indique simplement ce qui s'est passé. Je déteste param et comme objet nullable. J'ai créé le cours suivant

public sealed class Bag<TValue>
{
    public Bag(TValue value, bool hasValue = true)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Bag<TValue> Empty
    {
        get { return new Bag<TValue>(default(TValue), false); }
    }

    public bool HasValue { get; private set; }
    public TValue Value { get; private set; }
}

afin que je puisse écrire le code suivant

    public static Bag<XElement> GetXElement(this XElement element, string elementName)
    {
        try
        {
            XElement result = element.Element(elementName);
            return result == null
                       ? Bag<XElement>.Empty
                       : new Bag<XElement>(result);
        }
        catch (Exception)
        {
            return Bag<XElement>.Empty;
        }
    }

Ressemble à nullable mais pas seulement pour le type de valeur

Un autre exemple

    public static Bag<string> TryParseString(this XElement element, string attributeName)
    {
        Bag<string> attributeResult = GetString(element, attributeName);
        if (attributeResult.HasValue)
        {
            return new Bag<string>(attributeResult.Value);
        }
        return Bag<string>.Empty;
    }

    private static Bag<string> GetString(XElement element, string attributeName)
    {
        try
        {
            string result = element.GetAttribute(attributeName).Value;
            return new Bag<string>(result);
        }
        catch (Exception)
        {
            return Bag<string>.Empty;
        }
    }
GSerjo
la source
3
try catchfera des ravages sur votre performance si vous appelez GetXElement()et échouez plusieurs fois.
Robert Harvey
parfois, cela n'a pas d'importance. Jetez un coup d'œil à Bag Call. Merci pour votre observation
votre sac <T> est presque identique à System.Nullable <T> aussi appelé "objet
nullable
oui, presque public struct Nullable<T> where T : structla différence principale dans une contrainte. BTW la dernière version ici est github.com/Nelibur/Nelibur/blob/master/Source/Nelibur.Sword/...
GSerjo
0

Si vous êtes intéressé par l'itinéraire "valeur magique", une autre solution consiste à surcharger l'objectif de la classe Lazy. Bien que Lazy ait pour but de différer l’instanciation, rien ne vous empêche réellement d’utiliser une option similaire à Maybe ou Option. Par exemple:

    public static Lazy<TValue> GetValue<TValue, TKey>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key)
    {
        TValue retVal;
        if (dictionary.TryGetValue(key, out retVal))
        {
            var retValRef = retVal;
            var lazy = new Lazy<TValue>(() => retValRef);
            retVal = lazy.Value;
            return lazy;
        }

        return new Lazy<TValue>(() => default(TValue));
    }
zumalifeguard
la source