J'écris un type d'implémentation de file d'attente qui a une TryDequeue
méthode qui utilise un modèle similaire à diverses TryParse
méthodes .NET , où je retourne une valeur booléenne si l'action a réussi, et utilise un out
paramètre pour renvoyer la valeur réelle retirée de la file d'attente.
public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);
Maintenant, j'aime éviter les out
paramètres chaque fois que je le peux. C # 7 nous donne des délocalisations variables pour faciliter le travail avec eux, mais je considère toujours que les paramètres sont plus un mal nécessaire qu'un outil utile.
Le comportement que je veux de cette méthode est le suivant:
- S'il y a un article à retirer, renvoyez-le.
- S'il n'y a aucun élément à retirer (la file d'attente est vide), fournissez à l'appelant suffisamment d'informations pour agir correctement.
- Ne renvoyez pas simplement un élément nul s'il ne reste aucun élément.
- Ne lancez pas d'exception si vous essayez de retirer la file d'attente d'une file d'attente vide.
À l'heure actuelle, un appelant de cette méthode utilise presque toujours un modèle comme le suivant (en utilisant la syntaxe de variable C # 7 out):
if (myMessageQueue.TryDequeue(out Message dequeued))
MyMessagingClass.SendMessage(dequeued)
else
Console.WriteLine("No messages!"); // do other stuff
Ce qui n'est pas le pire, tout compte fait. Mais je ne peux pas m'empêcher de penser qu'il pourrait y avoir de meilleures façons de le faire (je suis tout à fait disposé à admettre qu'il pourrait ne pas y en avoir). Je déteste la façon dont l'appelant doit rompre son flux avec un conditionnel quand tout ce qu'il veut, c'est obtenir une valeur s'il en existe une.
Quels sont les autres modèles qui existent pour accomplir ce même comportement "essayer"?
Pour le contexte, cette méthode peut potentiellement être appelée dans les projets VB, donc des points bonus pour quelque chose qui fonctionne bien dans les deux. Ce fait devrait cependant avoir très peu de poids.
Option<T>
structure et renvoyez-la. Cesbool Try(..., out data)
fonctions sont une abomination.Réponses:
Utilisez un type Option, c'est-à-dire un objet d'un type qui a deux versions, généralement appelé "Some" (lorsqu'une valeur est présente) ou "None" (quand il n'y a pas de valeur) ... Ou occasionnellement ils s'appellent Just and Nothing. Il existe ensuite des fonctions dans ces types qui vous permettent d'accéder à la valeur si elle est présente, de tester la présence et, surtout, une méthode qui vous permet d'appliquer une fonction qui renvoie une option supplémentaire à la valeur si elle est présente (généralement en C # cela devrait être appelé FlatMap bien que dans d'autres langages, il soit souvent appelé Bind à la place ... Le nom est critique en C # parce que la méthode s de ce nom et de ce type vous permet d'utiliser vos objets Option dans les instructions LINQ).
Des fonctionnalités supplémentaires peuvent inclure des méthodes telles que IfPresent et IfNotPresent pour appeler des actions dans les conditions pertinentes, et OrElse (qui substitue une valeur par défaut lorsqu'aucune valeur n'est présente mais est un non op sinon), et ainsi de suite.
Votre exemple pourrait alors ressembler à ceci:
Il s'agit du modèle de monade Option (ou Peut-être), et il est extrêmement utile. Il existe des implémentations existantes (par exemple https://github.com/nlkl/Optional/blob/master/README.md ) mais ce n'est pas difficile non plus pour vous-même.
(Vous souhaiterez peut-être étendre ce modèle afin de renvoyer une description de la cause de l'erreur au lieu de rien lorsque la méthode échoue ... Ceci est entièrement réalisable et est souvent appelé la monade Either; comme son nom l'indique, vous pouvez utiliser le même FlatMap modèle pour le rendre facile à travailler avec dans ce cas aussi)
la source
Option
/Maybe
est qu'il s'agit d'une monade (si elle est implémentée en tant que telle, bien sûr), ce qui signifie qu'elle peut être chaînée, enveloppée, déballée et traitée en toute sécurité sans jamais avoir à gérer directement les différents cas, et ce chaînage et le traitement peut être effectué avec LINQ Query Expressions.flatMap
/bind
est appeléSelectMany
dans .NET.Try...
les conventions de dénomination dans .NET (et risquez de confondre les responsables).Les réponses données sont bonnes et j'irais avec l'une d'entre elles. Considérez cette réponse comme remplissant simplement quelques coins avec quelques idées alternatives:
Faites des sous-classes de
Message
appelésSomeMessage
etNoMessage
.Dequeue
peut maintenant retournerNoMessage
s'il n'y a pas de message etSomeMessage
s'il y a un message. Si l'appelé se soucie de détecter dans quel cas il se trouve, il peut facilement le faire par inspection de type. S'ils ne le font pas, eh bien, faites juste unNoMessage
faites rien quand une de ses méthodes est appelée, et hé, ils obtiennent ce qu'ils ont demandé.Identique à ce qui précède, mais rend
Message
implicitement convertible enbool
(ou implémenteoperator true / operator false
). Maintenant, vous pouvez direif (message)
et faire en sorte que le message soit bon et faux s'il est mauvais. (C'est presque certainement une mauvaise idée, mais je l'inclus pour être complet!)C # 7 a des tuples. Retourne un
(bool, Message)
tuple.Faites
Message
un type struct et retournezMessage?
.la source
En C # 7, vous pouvez utiliser la correspondance de motifs pour obtenir les mêmes résultats de manière plus élégante:
Dans ce cas particulier, j'utiliserais probablement des événements plutôt que d'interroger la file d'attente à plusieurs reprises.
la source
TryDequeue
de renvoyer une interface de marqueur avec uneMessage
implémentation et une implémentation de "rien"? Bien sûr, c'est plus élégant sur le site d'appel, mais vous êtes bloqué en implémentant 3 implémentations dans le code réel, qui lui-même peut être impossible s'ilMessage
s'agit d'un type existant.Il n'y a rien de mal à une méthode Try ... (ayant un paramètre out) pour un scénario dans lequel l'échec (aucune valeur) est tout aussi courant que le succès.
Mais, si vous insistez pour faire les choses différemment, vous voudrez peut-être renvoyer un message vide et soit l'envoyer (ce n'est plus votre problème) ou reporter l'instruction if jusqu'à ce que vous deviez vraiment savoir si vous avez un message avec du contenu ou non.
Notez que quelqu'un doit faire le test de toute façon. Je dirais que le test devrait être fait quand et où la question est actuelle. C'est au moment de la déque.
la source