«Utiliser» est-il approprié dans un contexte où il n'y a rien à éliminer?

9

En C #, l' usinginstruction est utilisée pour disposer de manière déterministe des ressources sans attendre le garbage collector. Par exemple, il peut être utilisé pour:

  • Éliminer les commandes ou connexions SQL,

  • Fermer les flux, libérer la source sous-jacente comme un fichier,

  • Éléments GDI + gratuits,

  • etc.

J'ai remarqué que cela usingest de plus en plus utilisé dans les cas où il n'y a rien à disposer, mais où il est juste plus pratique pour l'appelant d'écrire un usingbloc plutôt que deux commandes distinctes.

Exemples:

  • MiniProfiler , écrit par l'équipe Stack Overflow, utilise usingpour désigner les blocs à profiler:

    using (profiler.Step("Name goes here"))
    {
        this.DoSomethingUseful(i - 1);
    }
    

    Une approche alternative serait d'avoir deux blocs:

    var p = profiler.Start("Name goes here");
    this.DoSomethingUseful(i - 1);
    profiler.Stop(p);
    

    Une autre approche consisterait à utiliser des actions:

    profiler.Step("Name goes here", () => this.DoSomethingUseful(i - 1));
  • ASP.NET MVC a également choisi usingpour les formulaires:

    <% using (Html.BeginForm())
       { %>
           <label for="firstName">Name:</label>
           <%= Html.TextBox("name")%>
           <input type="submit" value="Save" />    
    <% } %>
    

Une telle utilisation est-elle appropriée? Comment le justifier, compte tenu de plusieurs inconvénients:

  • Les débutants seraient perdus, car une telle utilisation ne correspond pas à celle qui est expliquée dans les livres et les spécifications linguistiques,

  • Le code doit être expressif. Ici, l'expressivité souffre, car l'utilisation appropriée de usingest de montrer que derrière, il y a une ressource, comme un flux, une connexion réseau ou une base de données qui doit être libérée sans attendre le garbage collector.

Arseni Mourzenko
la source
2
L'approche consistant à avoir deux instructions individuelles souffre du même problème que la gestion naïve des ressources sans using: Cette dernière instruction (par exemple profiler.Stop(p)) n'est pas garantie d'être exécutée face aux exceptions et au flux de contrôle.
1
@delnan: cela explique pourquoi MiniProfiler a évité la seconde approche. Mais le deuxième doit être évité dans tous les cas, car il est difficile à écrire et à maintenir.
Arseni Mourzenko
1
Connexe: stackoverflow.com/questions/9472304
Robert Harvey
1
Connexe: grabbagoft.blogspot.com/2007/06/…
Robert Harvey

Réponses:

7

Votre dernière déclaration - que «l'utilisation appropriée de l'utilisation est de montrer que derrière, il y a une ressource, comme un flux, une connexion réseau ou une base de données qui doit être libérée sans attendre le garbage collector» est incorrecte, et la raison pour laquelle est donnée dans la documentation de l'interface IDisposable: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

L'utilisation principale de cette interface est de libérer des ressources non gérées.

Donc, si votre classe utilise des ressources non gérées, peu importe que vous souhaitiez ou non que GC se produise - cela n'a rien à voir avec GC car les ressources non gérées ne sont pas GC ( https: // stackoverflow. com / questions / 3607213 / qu'est-ce-que-signifie-géré-par-ressources-non-gérées-en-net ).

Donc, le but de "l'utilisation" n'est pas d'éviter d'attendre sur GC, c'est de forcer la libération de ces ressources non gérées maintenant , avant que l'instance de classe ne soit hors de portée et que Finalize soit appelé. Ceci est important pour des raisons qui devraient être évidentes - une ressource non gérée peut avoir des dépendances sur d'autres ressources non gérées, et si elles sont éliminées (ou finalisées) dans le mauvais ordre, de mauvaises choses peuvent se produire.

Donc, si la classe instanciée dans le bloc using utilise des ressources non gérées, alors la réponse est oui - c'est approprié.

Notez que IDisposable n'est pas prescriptif car il est uniquement destiné à libérer des ressources non gérées - juste que c'est son objectif principal . Il se peut que l'auteur de la classe ait une action qu'il souhaite appliquer à un certain moment, et l'implémentation d'IDisposable peut être un moyen d'y arriver, mais que ce soit ou non une solution élégante est quelque chose qui peut recevoir une réponse au cas par cas.

Quoi qu'il en soit, l'utilisation de "using" implique que la classe implémente IDisposable, de sorte qu'elle ne viole pas l'expressivité du code; cela montre clairement ce qui se passe, en fait.

Maximus Minimus
la source
"La principale utilisation de cette interface est de libérer des ressources non gérées." Je dis que la documentation de Microsoft est nulle sur ce point. C'est peut-être ainsi qu'ils l' utilisent dans le FCL, mais les développeurs d'applications l'utilisent pour une variété de choses depuis près d'une décennie maintenant - certains pour de bonnes raisons, d'autres pour des raisons pas si bonnes. Mais citer ce morceau de leur doc ne répond pas du tout à la question du PO.
Jesse C. Slicer
1
Notez mon avant-dernier paragraphe - le but principal n'a pas besoin d'être le seul but. IDisposable peut être implémenté pour quelque raison que ce soit, et il fournit une interface connue avec un comportement et un style d'utilisation attendus, donc quand vous le voyez, vous savez qu'il y a quelque chose d'autre qui doit se produire avant que l'instance ne puisse être laissée hors de portée. Le point est: s'il implémente IDisposable, alors l'utilisation est appropriée; tout le reste n'est qu'un préambule.
Maximus Minimus
1
Vous dites essentiellement que le but n'est pas d'éviter d'attendre GC, c'est d'éviter d'attendre le finaliseur. Mais je ne vois pas la différence: le finaliseur est appelé par le GC.
svick
Le GC fonctionne de manière non déterministe - vous ne savez pas quand il sera appelé. Le GC appelle également simplement le finaliseur, mais il ne se débarrasse pas de lui-même - la ressource réelle elle-même est hors du contrôle du GC. Avec l'utilisation de (1) l'objet est correctement délimité et (2) la suppression est connue pour se produire à un moment connu - quand il quitte la portée du bloc using, la ressource non gérée a disparu (ou - dans le pire des cas - est remise à quelque chose d'autre qui le détruira). Vraiment, ce n'est qu'une piqûre; lisez la documentation, c'est tout ce qu'il y a ne devrait pas avoir besoin d'être répété.
Maximus Minimus du
8

L'utilisation de usingimplique la présence d'une Dispose()méthode. D'autres programmeurs supposeront qu'une telle méthode existe sur l'objet. Par conséquent, si un objet n'est pas jetable, vous ne devez pas l'utiliser usingdessus.

La clarté du code est primordiale. Soit omettez le using, soit implémentez IDisposablesur l'objet.

MiniProfiler semble utiliser usingcomme mécanisme pour «clôturer» le code profilé. Il y a un certain mérite à cela; vraisemblablement, MiniProfiler appelle soit Dispose()pour arrêter un minuteur, soit le minuteur est arrêté lorsque l'objet MiniProfiler sort de la portée.

Plus généralement, vous invoquerez usinglorsqu'une sorte de finalisation doit se produire automatiquement. La documentation de html.BeginFormindique que, lorsque la méthode est utilisée dans une usinginstruction, elle restitue la </form>balise de fermeture à la fin du usingbloc.

Cela ne signifie pas nécessairement que ce n'est toujours pas un abus.

Robert Harvey
la source
4
Jusqu'ici tout à fait évident. La question est de savoir quand (le cas échéant) est-il approprié de mettre en œuvre IDisposable/ Dispose()bien qu'il n'ait rien à disposer dans le sens décrit par OP?
2
Je pensais l'avoir précisé; il n'y a pas de moment où cela est approprié.
Robert Harvey
1
Mon point est, Disposeest nécessaire pour usingavoir du sens du tout (indépendamment du fait que ce soit approprié). Les exemples de PO implémentent tous Dispose, n'est-ce pas? Et IIUC, la question est de savoir s'il est correct que ces classes utilisent Dispose, plutôt qu'une autre méthode (comme Stop()pour MiniProfiler).
1
Ouais, mais ce n'est pas la question. La question est de savoir si c'est une bonne idée de le faire.
2
@delnan: Encore une fois, je pensais que c'était clair. Si cela rend l'intention de votre code plus claire, c'est une bonne idée. Si ce n'est pas le cas, ce n'est pas le cas.
Robert Harvey
1

usingest sûr d'erreur et d'exception. Il garantit que Dispose()sera appelé indépendamment de toute erreur du programmeur. Il n'interfère pas avec la capture ou la gestion des exceptions, mais la Dispose()méthode est exécutée récursivement dans la pile lorsqu'une exception est levée.

Objets qui implémentent IDisposemais n'ont rien à disposer lorsqu'ils sont terminés. Sont au mieux, à l'épreuve du temps leur conception. Pour que vous n'ayez pas à refactoriser votre code source, alors qu'à l'avenir, ils doivent alors disposer de quelque chose.

Reactgular
la source