Quelque chose que j'ai souvent utilisé en C ++ était de laisser une classe A
gérer une condition d'entrée et de sortie d'état pour une autre classe B
, via le A
constructeur et le destructeur, pour s'assurer que si quelque chose dans cette portée lançait une exception, alors B aurait un état connu lorsque le portée a été quittée. Ce n'est pas du RAII pur pour l'acronyme, mais c'est néanmoins un modèle établi.
En C #, je veux souvent faire
class FrobbleManager
{
...
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
this.Frobble.Lock();
}
}
Ce qui doit être fait comme ça
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
try
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
finally
{
this.Frobble.Lock();
}
}
si je veux garantir l' Frobble
état lors du FiddleTheFrobble
retour. Le code serait plus agréable avec
private void FiddleTheFrobble()
{
using (var janitor = new FrobbleJanitor(this.Frobble))
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
}
où FrobbleJanitor
ressemble à peu près aiment
class FrobbleJanitor : IDisposable
{
private Frobble frobble;
public FrobbleJanitor(Frobble frobble)
{
this.frobble = frobble;
this.frobble.Unlock();
}
public void Dispose()
{
this.frobble.Lock();
}
}
Et c'est comme ça que je veux le faire. Maintenant, la réalité rattrape, car ce que je veux utiliser nécessite que le FrobbleJanitor
soit utilisé avec using
. Je pourrais considérer cela comme un problème de révision de code, mais quelque chose me harcèle.
Question: Est-ce que ce qui précède serait considéré comme une utilisation abusive de using
et IDisposable
?
la source
Réponses:
Je ne pense pas nécessairement. IDisposable est techniquement destiné à être utilisé pour des choses qui ont des ressources non gérées, mais alors la directive using n'est qu'une manière élégante d'implémenter un modèle commun de
try .. finally { dispose }
.Un puriste dirait «oui - c'est abusif», et dans le sens puriste c'est le cas; mais la plupart d'entre nous ne codent pas dans une perspective puriste, mais semi-artistique. Utiliser la construction «utiliser» de cette manière est en effet assez artistique, à mon avis.
Vous devriez probablement coller une autre interface au-dessus de IDisposable pour le pousser un peu plus loin, en expliquant aux autres développeurs pourquoi cette interface implique IDisposable.
Il existe de nombreuses autres alternatives pour faire cela, mais, en fin de compte, je ne peux penser à aucune qui soit aussi soignée que celle-ci, alors allez-y!
la source
using(Html.BeginForm())
monsieur? ne me dérange pas si je fais monsieur! :)Je considère cela comme un abus de la déclaration d'utilisation. Je sais que je suis minoritaire à ce poste.
Je considère que c'est un abus pour trois raisons.
Premièrement, parce que je m'attends à ce que "utiliser" soit utilisé pour utiliser une ressource et en disposer une fois que vous en avez terminé . Changer l'état du programme n'utilise pas une ressource et la modifier ne supprime rien. Par conséquent, «utiliser» pour muter et restaurer l'état est un abus; le code est trompeur pour le lecteur occasionnel.
Deuxièmement, parce que je m'attends à ce que «utiliser» soit utilisé par politesse et non par nécessité . La raison pour laquelle vous utilisez "using" pour supprimer un fichier lorsque vous en avez terminé n'est pas parce qu'il est nécessaire de le faire, mais parce que c'est poli - quelqu'un d'autre attend peut-être pour utiliser ce fichier, en disant "terminé maintenant "est la chose moralement correcte à faire. J'espère que je devrais être en mesure de refactoriser une "utilisation" de sorte que la ressource utilisée soit conservée plus longtemps, et éliminée plus tard, et que le seul impact de le faire soit de gêner légèrement les autres processus . Un bloc "using" qui a un impact sémantique sur l'état du programme est abusif parce qu'il cache une mutation importante et nécessaire de l'état du programme dans une construction qui semble être là pour la commodité et la politesse, pas pour la nécessité.
Et troisièmement, les actions de votre programme sont déterminées par son état; la nécessité d'une manipulation soigneuse de l'état est précisément la raison pour laquelle nous avons cette conversation en premier lieu. Voyons comment nous pourrions analyser votre programme original.
Étiez-vous pour apporter ceci à une révision de code dans mon bureau, la première question que je poserais est "est-il vraiment correct de verrouiller le frobble si une exception est levée?" Il est manifestement évident à partir de votre programme que cette chose re-verrouille agressivement le frobble quoi qu'il arrive. Est-ce correct? Une exception a été levée. Le programme est dans un état inconnu. Nous ne savons pas si Foo, Fiddle ou Bar ont jeté, pourquoi ils ont jeté, ou quelles mutations ils ont effectuées dans un autre état qui n'ont pas été nettoyés. Pouvez-vous me convaincre que dans cette terrible situation, c'est toujours la bonne chose à faire de refermer?
C'est peut-être le cas, peut-être que ce n'est pas le cas. Ce que je veux dire, c'est qu'avec le code tel qu'il a été écrit à l'origine, le réviseur de code sait poser la question . Avec le code qui utilise "using", je ne sais pas poser la question; Je suppose que le bloc "using" alloue une ressource, l'utilise un peu et s'en débarrasse poliment quand il est fait, pas que l' accolade fermante du bloc "using" mute l'état de mon programme dans une circonstance exceptionnelle quand arbitrairement plusieurs les conditions de cohérence de l'état du programme ont été violées.
L'utilisation du bloc "using" pour avoir un effet sémantique rend ce programme fragmenté:
extrêmement significatif. Quand je regarde cette attelle simple, je ne pense pas immédiatement que "cette attelle a des effets secondaires qui ont des répercussions profondes sur l'état global de mon programme". Mais quand vous abusez de "l'utilisation" comme ça, tout à coup, c'est le cas.
La deuxième chose que je demanderais si j'ai vu votre code original est "que se passe-t-il si une exception est levée après le déverrouillage mais avant que l'essai ne soit entré?" Si vous exécutez un assembly non optimisé, le compilateur a peut-être inséré une instruction no-op avant l'essai, et il est possible qu'une exception d'abandon de thread se produise sur le no-op. C'est rare, mais cela se produit dans la vraie vie, en particulier sur les serveurs Web. Dans ce cas, le déverrouillage se produit mais le verrouillage ne se produit jamais, car l'exception a été levée avant l'essai. Il est tout à fait possible que ce code soit vulnérable à ce problème et qu'il doive en fait être écrit
Encore une fois, peut-être que oui, peut-être pas, mais je sais poser la question . Avec la version "using", il est sensible au même problème: une exception d'abandon de thread pourrait être levée après que le Frobble soit verrouillé mais avant que la région try-protected associée à l'utilisation ne soit entrée. Mais avec la version "using", je suppose que c'est un "et alors?" situation. C'est malheureux si cela se produit, mais je suppose que l '«utilisation» n'est là que pour être poli, pas pour muter un état de programme d'une importance vitale. Je suppose que si une terrible exception d'abandon de thread se produit exactement au mauvais moment, alors, eh bien, le garbage collector nettoiera finalement cette ressource en exécutant le finaliseur.
la source
using
s'agit d'une question de politesse plutôt que de rectitude. Je crois que les fuites de ressources sont des problèmes d'exactitude, bien que souvent elles soient suffisamment mineures pour ne pas être des bogues hautement prioritaires. Ainsi, lesusing
blocs ont déjà un impact sémantique sur l'état du programme, et ce qu'ils empêchent n'est pas seulement un «inconvénient» pour d'autres processus mais peut-être un environnement cassé. Cela ne veut pas dire que le type différent d'impact sémantique impliqué par la question est également approprié.using
c'est parce que c'est un tisserand orienté vers l'aspect d'un pauvre homme en C #. Ce que le développeur veut vraiment, c'est un moyen de garantir que du code commun s'exécutera à la fin d'un bloc sans avoir à dupliquer ce code. C # ne prend pas en charge AOP aujourd'hui (MarshalByRefObject et PostSharp ne résistent pas). Cependant, la transformation de l'instruction using par le compilateur permet d'obtenir un AOP simple en réutilisant la sémantique de la disposition déterministe des ressources. Bien que ce ne soit pas une bonne pratique, il est parfois attrayant de laisser passer .....using
est trop attrayant pour être abandonné. Le meilleur exemple auquel je puisse penser est de suivre et de rapporter les horaires du code:using( TimingTrace.Start("MyMethod") ) { /* code */ }
encore une fois, c'est AOP -Start()
capture l'heure de début avant le début du bloc,Dispose()
capture l'heure de fin et enregistre l'activité. Il ne change pas l'état du programme et est indépendant des exceptions. C'est aussi attrayant car cela évite un peu la plomberie et son utilisation fréquente comme modèle atténue la sémantique déroutante.Si vous voulez juste un code propre et étendu, vous pouvez également utiliser des lambdas, á la
où la
.SafeExecute(Action fribbleAction)
méthode encapsule le bloctry
-catch
-finally
.la source
SafeExecute
c'est uneFribble
méthode qui appelleUnlock()
etLock()
dans le try / finally enveloppé. Mes excuses alors. J'ai immédiatement vuSafeExecute
comme une méthode d'extension générique et j'ai donc mentionné l'absence d'état d'entrée et de sortie. Ma faute.Eric Gunnerson, qui faisait partie de l'équipe de conception du langage C #, a donné cette réponse à à peu près la même question:
la source
using
fonction était pas à se limiter à l' élimination des ressources.C'est une pente glissante. IDisposable a un contrat, celui qui est soutenu par un finaliseur. Un finaliseur est inutile dans votre cas. Vous ne pouvez pas forcer le client à utiliser l'instruction using, encouragez-le seulement à le faire. Vous pouvez le forcer avec une méthode comme celle-ci:
Mais cela a tendance à devenir un peu gênant sans lamdas. Cela dit, j'ai utilisé IDisposable comme vous l'avez fait.
Il y a cependant un détail dans votre message qui rend cela dangereusement proche d'un anti-modèle. Vous avez mentionné que ces méthodes peuvent lever une exception. Ce n'est pas quelque chose que l'appelant peut ignorer. Il peut faire trois choses à ce sujet:
Les deux derniers nécessitent que l'appelant écrive explicitement un bloc try. Maintenant, l'instruction using se met en travers. Cela peut induire un client dans le coma qui lui fait croire que votre classe prend soin de l'état et qu'aucun travail supplémentaire n'est à faire. Ce n'est presque jamais exact.
la source
SafeExecute
comme une méthode d'extension générique. Je vois maintenant qu'il s'agissait probablement d'uneFribble
méthode qui appelleUnlock()
etLock()
dans le try / finally enveloppé. Ma faute.Un exemple concret est le BeginForm d'ASP.net MVC. En gros, vous pouvez écrire:
ou
Html.EndForm appelle Dispose et Dispose génère simplement la
</form>
balise. La bonne chose à ce sujet est que les crochets {} créent une "portée" visible qui permet de voir plus facilement ce qu'il y a dans le formulaire et ce qui ne l'est pas.Je n'en abuserais pas, mais essentiellement IDisposable est juste une façon de dire "Vous DEVEZ appeler cette fonction lorsque vous en avez terminé avec elle". MvcForm l'utilise pour s'assurer que le formulaire est fermé, Stream l'utilise pour s'assurer que le flux est fermé, vous pouvez l'utiliser pour vous assurer que l'objet est déverrouillé.
Personnellement, je ne l'utiliserais que si les deux règles suivantes sont vraies, mais celles-ci sont définies arbitrairement par moi:
À la fin, tout est question d'attentes. Si un objet implémente IDisposable, je suppose qu'il doit faire un nettoyage, donc je l'appelle. Je pense qu'il vaut généralement mieux avoir une fonction "Shutdown".
Cela étant dit, je n'aime pas cette ligne:
Comme le FrobbleJanitor "possède" le Frobble maintenant, je me demande s'il ne serait pas préférable d'appeler Fiddle à la place pour Frobble dans le concierge?
la source
Nous utilisons beaucoup ce modèle dans notre base de code, et je l'ai déjà vu partout auparavant - je suis sûr qu'il a dû être discuté ici également. En général, je ne vois pas ce qui ne va pas à faire cela, cela fournit un modèle utile et ne cause aucun mal réel.
la source
Je suis d'accord avec la plupart des gens ici pour dire que c'est fragile, mais utile. Je voudrais vous indiquer la classe System.Transaction.TransactionScope , qui fait quelque chose comme vous le souhaitez.
En général, j'aime la syntaxe, elle supprime beaucoup de fouillis de la vraie viande. Pensez cependant à donner un bon nom à la classe d'assistance - peut-être ... Portée, comme dans l'exemple ci-dessus. Le nom doit indiquer qu'il encapsule un morceau de code. * Scope, * Block ou quelque chose de similaire devrait le faire.
la source
Remarque: mon point de vue est probablement biaisé par rapport à mon arrière-plan C ++, donc la valeur de ma réponse doit être évaluée par rapport à ce biais possible ...
Que dit la spécification du langage C #?
Citant la spécification du langage C # :
Le code qui utilise une ressource est, bien sûr, le code commençant par le
using
mot - clé et allant jusqu'à la portée attachée auusing
.Donc je suppose que c'est ok parce que le verrou est une ressource.
Peut-être que le mot-clé a
using
été mal choisi. Peut-être qu'il aurait dû être appeléscoped
.Ensuite, nous pouvons considérer presque tout comme une ressource. Un descripteur de fichier. Une connexion réseau ... Un fil?
Un fil ???
Utiliser (ou abuser) du
using
mot-clé?Serait-il intéressant (ab) d'utiliser le
using
mot - clé pour s'assurer que le travail du thread est terminé avant de quitter l'étendue?Herb Sutter semble penser que c'est brillant , car il propose une utilisation intéressante du modèle IDispose pour attendre la fin du travail d'un thread:
http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095
Voici le code, copié-collé de l'article:
Alors que le code C # pour l'objet Active n'est pas fourni, il est écrit, le C # utilise le modèle IDispose pour le code dont la version C ++ est contenue dans le destructeur. Et en regardant la version C ++, nous voyons un destructeur qui attend la fin du thread interne avant de quitter, comme le montre cet autre extrait de l'article:
Donc, en ce qui concerne Herb, c'est brillant .
la source
Je pense que la réponse à votre question est non, ce ne serait pas un abus
IDisposable
.La façon dont je comprends l'
IDisposable
interface est que vous n'êtes pas censé travailler avec un objet une fois qu'il a été supprimé (sauf que vous êtes autorisé à appeler saDispose
méthode aussi souvent que vous le souhaitez).Étant donné que vous créez un nouveau
FrobbleJanitor
explicitement chaque fois que vous accédez à l'using
instruction, vous n'utilisez jamaisFrobbeJanitor
deux fois le même objet. Et puisque son but est de gérer un autre objet,Dispose
semble approprié à la tâche de libérer cette ressource («gérée»).(Btw., L'exemple de code standard démontrant la bonne implémentation de
Dispose
suggère presque toujours que les ressources gérées doivent être libérées, pas seulement les ressources non gérées telles que les descripteurs de système de fichiers.)La seule chose qui m'inquiète personnellement, c'est que ce qui se passe est moins clair
using (var janitor = new FrobbleJanitor())
qu'avec untry..finally
bloc plus explicite où les opérationsLock
&Unlock
sont directement visibles. Mais quelle approche est adoptée se résume probablement à une question de préférences personnelles.la source
Ce n'est pas abusif. Vous les utilisez ce pour quoi ils ont été créés. Mais vous devrez peut-être considérer l'un sur l'autre en fonction de vos besoins. Par exemple, si vous optez pour 'art' alors vous pouvez utiliser 'using' mais si votre morceau de code est exécuté beaucoup de fois, alors pour des raisons de performances, vous pouvez utiliser les constructions 'try' .. 'finally'. Parce que «utiliser» implique généralement la création d'un objet.
la source
Je pense que vous l'avez bien fait. La surcharge de Dispose () serait un problème que la même classe avait ultérieurement nettoyé, et la durée de vie de ce nettoyage a changé pour être différente de celle du moment où vous prévoyez de maintenir un verrou. Mais comme vous avez créé une classe distincte (FrobbleJanitor) qui est uniquement responsable du verrouillage et du déverrouillage du Frobble, les choses sont suffisamment découplées pour que vous ne rencontriez pas ce problème.
Je renommerais FrobbleJanitor cependant, probablement en quelque chose comme FrobbleLockSession.
la source