Dois-je appeler Close () ou Dispose () pour les objets de flux?

151

Classes telles que Stream, StreamReader, StreamWriteretc outils IDisposableinterface. Cela signifie que nous pouvons appeler la Dispose()méthode sur les objets de ces classes. Ils ont également défini une publicméthode appelée Close(). Maintenant, cela me trouble, que dois-je appeler une fois que j'ai fini avec les objets? Et si j'appelle les deux?

Mon code actuel est le suivant:

using (Stream responseStream = response.GetResponseStream())
{
   using (StreamReader reader = new StreamReader(responseStream))
   {
      using (StreamWriter writer = new StreamWriter(filename))
      {
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         {
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            {
               writer.Write(buffer, 0, count);
            }
         }
         writer.Close();
      }
      reader.Close();
   }
}

Comme vous le voyez, j'ai écrit des using()constructions, qui appellent automatiquement la Dispose()méthode sur chaque objet. Mais j'appelle aussi des Close()méthodes. Est ce juste?

Veuillez me suggérer les meilleures pratiques lors de l'utilisation d'objets de flux. :-)

L'exemple MSDN n'utilise pas les using()constructions et la Close()méthode d' appel :

Est-ce bien?

Nawaz
la source
Si vous utilisez ReSharper, vous pouvez le définir comme un "antipattern" dans le catalogue de motifs. ReSharper marquera chaque utilisation comme erreur / indice / avertissement concernant votre définition. Il est également possible de définir comment ReSharper doit appliquer un QuickFix pour une telle occurrence.
Thorsten Hans
3
Juste un conseil: vous pouvez utiliser l'instruction using comme celle-ci pour plusieurs itens jetables: using (Stream responseStream = response.GetResponseStream ()) using (StreamReader reader = new StreamReader (responseStream)) using (StreamWriter writer = new StreamWriter (filename)) {//...Some code}
Latrova
Vous n'avez pas besoin d'imbriquer vos instructions using comme cela, vous pouvez les empiler les unes sur les autres et avoir un jeu de crochets. Dans un autre article, j'ai suggéré une modification pour un extrait de code qui aurait dû contenir des instructions d'utilisation avec cette technique si vous souhaitez regarder et corriger votre "flèche de code": stackoverflow.com/questions/5282999/…
Timothy Gonzalez
2
@ Suncat2000 Vous pouvez avoir plusieurs instructions using, mais ne pas les imbriquer et les empiler à la place. Je ne veux pas cette syntaxe qui limite le type: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Je veux dire comme ça où vous pouvez redéfinir le type:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

Réponses:

101

Un saut rapide dans Reflector.NET montre que la Close()méthode sur StreamWriterest:

public override void Close()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

Et StreamReaderc'est:

public override void Close()
{
    this.Dispose(true);
}

Le Dispose(bool disposing)remplacement dans StreamReaderest:

protected override void Dispose(bool disposing)
{
    try
    {
        if ((this.Closable && disposing) && (this.stream != null))
        {
            this.stream.Close();
        }
    }
    finally
    {
        if (this.Closable && (this.stream != null))
        {
            this.stream = null;
            /* deleted for brevity */
            base.Dispose(disposing);
        }
    }
}

La StreamWriterméthode est similaire.

Ainsi, en lisant le code, il est clair que vous pouvez appeler Close()et Dispose()diffuser des flux aussi souvent que vous le souhaitez et dans n'importe quel ordre. Cela ne changera en rien le comportement.

Il s'agit donc de savoir s'il est ou non plus lisible à utiliser Dispose(), Close()et / ou using ( ... ) { ... }.

Ma préférence personnelle est que cela using ( ... ) { ... }devrait toujours être utilisé lorsque cela est possible car cela vous aide à "ne pas courir avec des ciseaux".

Mais, bien que cela contribue à l'exactitude, cela réduit la lisibilité. En C #, nous avons déjà une pléthore d'accolades fermantes, alors comment savoir laquelle effectue réellement la fermeture sur le flux?

Donc je pense qu'il est préférable de faire ceci:

using (var stream = ...)
{
    /* code */

    stream.Close();
}

Cela n'affecte pas le comportement du code, mais facilite la lisibilité.

Énigmativité
la source
20
" En C #, nous avons déjà pléthore d'accolades fermantes alors comment savoir laquelle effectue réellement la fermeture sur le flux? " Je ne pense pas que ce soit un gros problème: le flux est fermé "au bon moment", c'est-à-dire, lorsque la variable sort du champ d'application et n'est plus nécessaire.
Heinzi
110
Hmm, non, c'est un "pourquoi diable le ferme-t-il deux fois ??" ralentisseur lors de la lecture.
Hans Passant
57
Je ne suis pas d'accord avec l' Close()appel redondant . Si quelqu'un moins expérimenté regarde le code et ne le sait pas, usingil: 1) le recherchera et l' apprendra , ou 2) ajoutera aveuglément un Close()manuellement. S'il choisit 2), peut-être qu'un autre développeur verra le redondant Close()et, au lieu de "rire", instruira le développeur le moins expérimenté. Je ne suis pas en faveur de rendre la vie difficile aux développeurs inexpérimentés, mais je suis en faveur de les transformer en développeurs expérimentés.
R. Martinho Fernandes
14
Si vous utilisez + Close () et activez / analysez, vous obtenez "avertissement: CA2202: Microsoft.Usage: L'objet 'f' peut être supprimé plusieurs fois dans la méthode 'Foo (string)'. Pour éviter de générer un System. ObjectDisposedException, vous ne devriez pas appeler Dispose plus d'une fois sur un objet. net.
marc40000
4
+1 pour la bonne réponse. Une autre chose à considérer. Pourquoi ne pas ajouter un commentaire après l'accolade fermante comme // Fermer ou comme je le fais, étant un débutant, j'ajoute une doublure après toute accolade fermante qui n'est pas claire. comme par exemple dans une classe longue, j'ajouterais // End Namespace XXX après l'accolade de fermeture finale, et // End Class YYY après la deuxième accolade de fermeture finale. N'est-ce pas à cela que servent les commentaires. Juste curieux. :) En tant que débutant, j'ai vu un tel code, hense pourquoi je suis venu ici. J'ai posé la question "Pourquoi la nécessité de la deuxième clôture". Je pense que les lignes de code supplémentaires n'ajoutent pas de clarté. Désolé.
Francis Rodgers
51

Non, vous ne devez pas appeler ces méthodes manuellement. A la fin du usingbloc la Dispose()méthode est automatiquement appelée ce qui prendra soin de libérer les ressources non managées (au moins pour les classes standard .NET BCL telles que les flux, les lecteurs / écrivains, ...). Vous pouvez donc également écrire votre code comme ceci:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        {
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            {
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 {
                     writer.Write(buffer, 0, count);
                 }
            }
         }

La Close()méthode appelle Dispose().

Darin Dimitrov
la source
1
Je suis presque sûr que vous n'avez pas besoin d'être usingle premier responseStreamcar cela est enveloppé par le readerqui s'assurera qu'il est fermé lorsque le lecteur est éliminé. +1 nontheless
Isak Savo
C'est déroutant quand vous avez dit The Close method calls Dispose.... et dans le reste de votre message, vous laissez entendre que Dispose()cela appellerait Close(), je ne devrais pas appeler ce dernier manuellement. Êtes-vous en train de dire qu'ils s'appellent?
Nawaz
@Nawaz, mon message était déroutant. La méthode Close appelle simplement Dispose. Dans votre cas, vous avez besoin de Dispose afin de libérer des ressources non gérées. En enveloppant votre code dans l'instruction using, la méthode Dispose est appelée.
Darin Dimitrov
3
Terrible réponse. Cela suppose que vous pouvez utiliser un usingbloc. J'implémente une classe qui écrit de temps en temps et qui ne le peut donc pas.
Jez
5
@Jez Votre classe doit alors implémenter l'interface IDisposable, et éventuellement aussi Close () si close est la terminologie standard dans la zone , afin que les classes utilisant votre classe puissent utiliser using(ou, encore une fois, opter pour le Dispose Pattern).
Dorus
13

La documentation dit que ces deux méthodes sont équivalentes:

StreamReader.Close : cette implémentation de Close appelle la méthode Dispose en passant une valeur vraie.

StreamWriter.Close : cette implémentation de Close appelle la méthode Dispose en passant une valeur vraie.

Stream.Close : cette méthode appelle Dispose, en spécifiant true pour libérer toutes les ressources.

Donc, les deux sont également valables:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename)) { 
   // do something
} 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try {
    // do something
}
finally {
    writer.Close();
}

Personnellement, je m'en tiendrai à la première option, car elle contient moins de "bruit".

Heinzi
la source
5

Sur de nombreuses classes prenant en charge les deux méthodes Close()et Dispose(), les deux appels seraient équivalents. Sur certaines classes, cependant, il est possible de rouvrir un objet qui a été fermé. Certaines de ces classes peuvent conserver certaines ressources actives après une fermeture, afin de permettre la réouverture; d'autres peuvent ne garder aucune ressource active Close(), mais peuvent activer un drapeau Dispose()pour interdire explicitement la réouverture.

Le contrat pour IDisposable.Disposeexige explicitement que l'appeler sur un objet qui ne sera plus jamais utilisé sera au pire inoffensif, donc je recommanderais d'appeler l'un IDisposable.Disposeou l' autre ou une méthode appelée Dispose()sur chaque IDisposableobjet, que l'on appelle également ou non Close().

supercat
la source
Pour info, voici un article sur les blogs MSDN qui explique le plaisir de Close and Dispose. blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieVoir le
1

C'est une vieille question, mais vous pouvez maintenant écrire en utilisant des instructions sans avoir à bloquer chacune d'elles. Ils seront éliminés dans l'ordre inverse lorsque le bloc contenant est terminé.

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)
{
    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    {
        writer.Write(buffer, 0, count);
    }
}

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using

Todd Skelton
la source