Est-il abusif d'utiliser IDisposable et «utiliser» comme un moyen d'obtenir un «comportement ciblé» pour la sécurité des exceptions?

112

Quelque chose que j'ai souvent utilisé en C ++ était de laisser une classe Agérer une condition d'entrée et de sortie d'état pour une autre classe B, via le Aconstructeur 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 FiddleTheFrobbleretour. 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
    }
}

FrobbleJanitorressemble à 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 FrobbleJanitorsoit 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 usinget IDisposable?

Johann Gerell
la source
23
+1 pour les noms de classe et de méthode seuls :-). Eh bien, pour être juste, et la vraie question.
Joey
2
@Johannes: Oui, je peux voir pourquoi - la plupart des gens se contentent de gâcher leur violon, mais je dois juste protester contre ça. ;-) Et, au fait, +1 pour la similitude de votre nom.
Johann Gerell
Peut-être pouvez-vous utiliser la portée lock (this) {..} pour obtenir le même résultat dans cet exemple?
2010
1
@ oɔɯǝɹ: Vous utilisez lock () pour empêcher l'accès simultané à quelque chose à partir de différents threads. Rien à voir avec le scénario que je décris.
Johann Gerell
1
IScopeable sans Finalizer dans la prochaine version de C # :)
Jon Raynor

Réponses:

33

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!

Andras Zoltan
la source
2
Je crois que c'est aussi une utilisation artistique de «l'utilisation». Je pense que le post de Samual Jack faisant un lien vers un commentaire d'Eric Gunnerson valide cette implémentation de "using". Je peux voir d'excellents points à la fois d'Andras et d'Eric à ce sujet.
IAbstract
1
J'en ai parlé à Eric Gunnerson il y a quelque temps et selon lui, c'était l'intention de l'équipe de conception C # que l'utilisation dénote des portées. En fait, il a postulé que s'ils avaient conçu l'utilisation avant l'instruction de verrouillage, il pourrait même ne pas y avoir d'instruction de verrouillage. Cela aurait été un bloc d'utilisation avec un appel Monitor ou quelque chose. MISE À JOUR: Je viens de réaliser que la réponse suivante est d'Eric Lippert qui fait également partie de l'équipe des langues. ;) Peut-être que l'équipe C # elle-même n'est pas entièrement d'accord à ce sujet? Le contexte de ma discussion avec Gunnerson était la classe TimedLock
Haacked le
2
@Haacked - Amusant n'est-ce pas; votre commentaire prouve totalement mon point; que vous aviez parlé à un gars de l'équipe et qu'il était tout à fait d'accord; puis soulignant Eric Lippert ci-dessous, de la même équipe en désaccord. De mon point de vue; C # fournit un mot-clé qui exploite une interface et produit également un joli modèle de code qui fonctionne dans de nombreux scénarios. Si nous ne sommes pas censés en abuser, alors C # devrait trouver un autre moyen de l'imposer. Dans tous les cas, les concepteurs doivent probablement aligner leurs canards ici! En attendant: using(Html.BeginForm())monsieur? ne me dérange pas si je fais monsieur! :)
Andras Zoltan
71

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

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

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.

Eric Lippert
la source
18
Même si je répondrais différemment à la question, je pense que c'est un article bien argumenté. Cependant, je conteste votre affirmation selon laquelle il usings'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, les usingblocs 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é.
kvb
13
Les fuites de ressources sont des problèmes d'exactitude, oui. Mais le fait de ne pas utiliser un "using" ne fait pas fuir la ressource; la ressource sera finalement nettoyée lors de l'exécution du finaliseur. Le nettoyage n'est peut-être pas aussi agressif et opportun que vous le souhaiteriez, mais cela se produira.
Eric Lippert
7
Merveilleux article, voici mes deux cents :) Je soupçonne que beaucoup d '"abus" de usingc'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 .....
LBushkin
11
Bien que je sois généralement d'accord avec votre position, il y a des cas où l'avantage de la réorientation usingest 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.
LBushkin
6
Drôle, j'ai toujours pensé à utiliser comme un moyen de soutenir RAII. Il me semble raisonnablement intuitif de considérer une serrure comme un type de ressource.
Brian
27

Si vous voulez juste un code propre et étendu, vous pouvez également utiliser des lambdas, á la

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

où la .SafeExecute(Action fribbleAction)méthode encapsule le bloc try- catch- finally.

herzmeister
la source
Dans cet exemple, vous avez manqué l'état d'entrée. De plus, assurez-vous de terminer essayez / enfin, mais vous n'appelez rien finalement, donc si quelque chose dans le lambda vous jette, vous n'avez aucune idée de l'état dans lequel vous vous trouvez.
Johann Gerell
1
Ah! Ok, je suppose que vous voulez dire que SafeExecutec'est une Fribbleméthode qui appelle Unlock()et Lock()dans le try / finally enveloppé. Mes excuses alors. J'ai immédiatement vu SafeExecutecomme 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.
Johann Gerell
1
Soyez très prudent avec cette approche. Il pourrait y avoir des prolongations de vie imprévues subtiles et dangereuses dans le cas des habitants capturés!
jason
4
Yuk. Pourquoi masquer essayer / attraper / enfin? Rend votre code caché pour être lu par les autres.
Frank Schwieterman
2
@Yukky Frank: Ce n'était pas mon idée de "cacher" quoi que ce soit, c'était la demande de l'interrogateur. :-P ... Cela dit, la demande est enfin une question de "Ne vous répétez pas". Vous pourriez avoir beaucoup de méthodes qui nécessitent le même "cadre" standard pour acquérir / publier proprement quelque chose et vous ne voulez pas imposer cela à l'appelant (pensez à l'encapsulation). On pourrait aussi nommer des méthodes comme .SafeExecute (...) plus clairement pour communiquer assez bien ce qu'elles font.
herzmeister
25

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:

Doug demande:

re: Une instruction de verrouillage avec timeout ...

J'ai déjà fait cette astuce pour traiter des modèles courants dans de nombreuses méthodes. Verrouillez généralement l' acquisition , mais il y en a d' autres . Le problème est que cela ressemble toujours à un hack puisque l'objet n'est pas vraiment jetable autant que " call-back-at-the-end-of-a-scope-able ".

Doug,

Quand nous avons décidé [sic] l'instruction using, nous avons décidé de la nommer «using» plutôt que quelque chose de plus spécifique à la suppression d'objets afin qu'elle puisse être utilisée exactement pour ce scénario.

Samuel Jack
la source
4
Eh bien, cette citation devrait dire à quoi il faisait référence quand il a dit «exactement ce scénario», car je doute qu'il se réfère à cette question future.
Frank Schwieterman
4
@Frank Schwieterman: J'ai complété le devis. Apparemment, les gens de l'équipe C # ne pense que la usingfonction était pas à se limiter à l' élimination des ressources.
paercebal
11

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:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

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:

  • Ne rien faire, l'exception n'est pas récupérable. Le cas normal. L'appel de Unlock n'a pas d'importance.
  • Attrapez et gérez l'exception
  • Restaurez l'état dans son code et laissez l'exception passer la chaîne d'appels.

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.

Hans Passant
la source
Le cas forcé a été donné par "herzmeister der welten" ci-dessus, je pense - même si l'OP ne semblait pas aimer l'exemple.
Benjamin Podszun
Oui, j'ai interprété le sien SafeExecutecomme une méthode d'extension générique. Je vois maintenant qu'il s'agissait probablement d'une Fribbleméthode qui appelle Unlock()et Lock()dans le try / finally enveloppé. Ma faute.
Johann Gerell
Ignorer une exception ne signifie pas qu'elle n'est pas récupérable. Cela signifie qu'il sera traité ci-dessus dans la pile. Par exemple, des exceptions peuvent être utilisées pour quitter un thread correctement. Dans ce cas, vous devez libérer correctement vos ressources, y compris les verrous verrouillés.
paercebal
7

Un exemple concret est le BeginForm d'ASP.net MVC. En gros, vous pouvez écrire:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

ou

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

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:

  • Dispose doit être une fonction qui doit toujours être exécutée, il ne doit donc y avoir aucune condition à l'exception des Null-Checks
  • Après Dispose (), l'objet ne doit pas être réutilisable. Si je voulais un objet réutilisable, je préfère lui donner des méthodes d'ouverture / fermeture plutôt que de disposer. Je lance donc une exception InvalidOperationException lorsque j'essaie d'utiliser un objet supprimé.

À 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:

this.Frobble.Fiddle();

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?

Michael Stum
la source
À propos de votre "Je n'aime pas cette ligne" - j'ai en fait brièvement pensé à le faire de cette façon lors de la formulation de la question, mais je ne voulais pas encombrer la sémantique de ma question. Mais je suis plutôt d'accord avec vous. En quelque sorte. :)
Johann Gerell
1
Approuvé pour avoir fourni un exemple de Microsoft lui-même. Notez qu'il existe une exception spécifique à lever dans le cas que vous mentionnez: ObjectDisposedException.
Antitoon
4

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.

Simon Steele
la source
4

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.

Benjamin Podszun
la source
1
Le "Janitor" vient d'un vieil article en C ++ d'Andrei Alexandrescu sur les protecteurs de lunette, avant même que ScopeGuard ne parvienne à Loki.
Johann Gerell
@Benjamin: J'aime la classe TransactionScope, je n'en avais jamais entendu parler. Peut-être parce que je suis sur le terrain .Net CF, où il n'est pas inclus ... :-(
Johann Gerell
4

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 # :

8.13 L'instruction using

[...]

Une ressource est une classe ou une structure qui implémente System.IDisposable, qui comprend une seule méthode sans paramètre nommée Dispose. Le code qui utilise une ressource peut appeler Dispose pour indiquer que la ressource n'est plus nécessaire. Si Dispose n'est pas appelé, la suppression automatique se produit finalement à la suite du garbage collection.

Le code qui utilise une ressource est, bien sûr, le code commençant par le usingmot - clé et allant jusqu'à la portée attachée au using.

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 usingmot-clé?

Serait-il intéressant (ab) d'utiliser le usingmot - 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:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

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:

~Active() {
    // etc.
    thd->join();
 }

Donc, en ce qui concerne Herb, c'est brillant .

Paercebal
la source
3

Je pense que la réponse à votre question est non, ce ne serait pas un abus IDisposable.

La façon dont je comprends l' IDisposableinterface est que vous n'êtes pas censé travailler avec un objet une fois qu'il a été supprimé (sauf que vous êtes autorisé à appeler sa Disposeméthode aussi souvent que vous le souhaitez).

Étant donné que vous créez un nouveau FrobbleJanitor explicitement chaque fois que vous accédez à l' usinginstruction, vous n'utilisez jamais FrobbeJanitordeux 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 Disposesuggè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 un try..finallybloc plus explicite où les opérations Lock& Unlocksont directement visibles. Mais quelle approche est adoptée se résume probablement à une question de préférences personnelles.

stakx - ne contribue plus
la source
1

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
1

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.

Frank Schwieterman
la source
À propos du changement de nom - J'ai utilisé "Janitor" pour la principale raison pour laquelle j'ai traité Lock / Unlock. Je pensais que cela rimait avec les autres choix de noms. ;-) Si je devais utiliser cette méthode comme modèle dans toute ma base de code, j'inclurais certainement quelque chose de plus génériquement descriptif, comme vous l'avez laissé entendre avec "Session". Si cela avait été l'ancien C ++, j'utiliserais probablement FrobbleUnlockScope.
Johann Gerell