130 votes

Une fuite de mémoire est-elle créée si un MemoryStream en .NET n'est pas fermé ?

J'ai le code suivant :

MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

Y a-t-il une chance que le MemoryStream que j'ai alloué ne soit pas éliminé plus tard ?

J'ai un examen par les pairs qui insiste pour que je ferme manuellement ce dossier, et je n'arrive pas à trouver les informations qui me permettraient de savoir si son point de vue est valable ou non.

43 votes

Demandez à votre examinateur exactement pourquoi il pense que vous devriez le fermer. S'il parle de bonnes pratiques générales, il est probablement intelligent. S'il parle de libérer la mémoire plus tôt, il a tort.

0 votes

Question connexe avec des opinions quelque peu différentes de celles présentées dans les réponses ici : MemoryStream.Close() ou MemoryStream.Dispose() .

183voto

Jon Skeet Points 692016

Vous ne fuirez rien - du moins dans l'implémentation actuelle.

L'appel à Dispose ne nettoiera pas plus rapidement la mémoire utilisée par MemoryStream. Il s'agit de se empêche votre flux d'être viable pour les appels de lecture/écriture après l'appel, ce qui peut ou non vous être utile.

Si vous êtes absolument sûr que vous jamais vous voulez passer d'un MemoryStream à un autre type de flux, vous ne risquez rien à ne pas appeler Dispose. Cependant, c'est généralement une bonne pratique, en partie parce que si jamais vous faire changer pour utiliser un autre Stream, vous ne voulez pas vous faire piquer par un bug difficile à trouver parce que vous avez choisi la solution de facilité dès le début. (D'un autre côté, il y a l'argument YAGNI...)

L'autre raison de le faire quand même est qu'une nouvelle implémentation mai introduire des ressources qui seraient libérées lors du Dispose.

0 votes

Dans ce cas, la fonction renvoie un MemoryStream parce qu'elle fournit "des données qui peuvent être interprétées différemment en fonction des paramètres d'appel", donc cela aurait pu être un tableau d'octets, mais il était plus facile pour d'autres raisons de le faire en tant que MemoryStream. Il ne s'agira donc certainement pas d'une autre classe Stream.

0 votes

Dans ce cas, j'aurais quand même essayez de s'en débarrasser juste par principe - pour prendre de bonnes habitudes, etc. - mais je ne m'inquiéterais pas trop si cela devenait délicat.

1 votes

Si vous êtes vraiment soucieux de libérer les ressources le plus rapidement possible, annulez la référence immédiatement après votre bloc "using", afin que les ressources non gérées (s'il y en a) soient nettoyées, et que l'objet devienne éligible à la collecte des déchets. Si la méthode retourne immédiatement, cela ne fera probablement pas de différence, mais si vous faites d'autres choses dans la méthode comme demander plus de mémoire, alors cela peut certainement faire une différence.

69voto

Rob Prouse Points 9193

Si un objet est jetable, vous devez toujours le jeter. Vous devez utiliser un using dans votre bar() pour s'assurer que ms2 est éliminé.

Il sera éventuellement nettoyé par le ramasseur d'ordures, mais il est toujours bon d'appeler Dispose. Si vous exécutez FxCop sur votre code, il le signalera comme un avertissement.

0 votes

Donc, même si je peux avoir un bloc "using", je dois toujours appeler .Dispose() ?

16 votes

Le bloc d'utilisation appelle l'élimination pour vous.

0 votes

Je ne suis pas d'accord avec ce conseil. Souvent Dispose est juste un no-op et l'appeler ne fait qu'encombrer votre code.

32voto

Triynko Points 5600

Oui, il y a a fuite selon la définition que vous donnez à une FUITE et le temps écoulé...

Si par "fuite" vous entendez "la mémoire reste allouée, non disponible pour l'utilisation, même si vous avez fini de l'utiliser" et par "dernière" vous entendez n'importe quel moment après avoir appelé "dispose", alors oui, il peut y avoir une fuite, bien qu'elle ne soit pas permanente (c'est-à-dire pour toute la durée d'exécution de vos applications).

Pour libérer la mémoire gérée utilisée par le MemoryStream, vous devez le déréférencer en annulant votre référence à celui-ci, de sorte qu'il devient immédiatement éligible à la collecte des déchets. Si vous ne le faites pas, vous créez une fuite temporaire à partir du moment où vous avez fini de l'utiliser, jusqu'à ce que votre référence sorte du champ d'application, car entre-temps la mémoire ne sera pas disponible pour l'allocation.

L'avantage de l'instruction using (par rapport au simple appel à la fonction dispose) est que vous pouvez DECLARE votre référence dans l'instruction using. À la fin de l'instruction using, non seulement l'instruction dispose est appelée, mais votre référence sort de sa portée, ce qui annule effectivement la référence et rend votre objet éligible à la collecte des déchets immédiatement, sans que vous ayez à vous souvenir d'écrire le code "reference=null".

Bien que le fait de ne pas déréférencer quelque chose immédiatement ne constitue pas une fuite de mémoire "permanente" classique, cela a certainement le même effet. Par exemple, si vous gardez votre référence au MemoryStream (même après avoir appelé dispose), et qu'un peu plus loin dans votre méthode vous essayez d'allouer plus de mémoire... la mémoire utilisée par votre flux de mémoire toujours référencé ne sera pas disponible pour vous jusqu'à ce que vous annuliez la référence ou qu'elle sorte de la portée, même si vous avez appelé dispose et que vous avez fini de l'utiliser.

7 votes

J'adore cette réponse. Parfois, les gens oublient la double fonction de l'utilisation : récupération avide des ressources. y déréférencement empressé.

1 votes

En effet, bien que j'aie entendu dire que, contrairement à Java, le compilateur C# détecte la "dernière utilisation possible", donc si la variable est destinée à sortir de la portée après sa dernière référence, elle peut devenir éligible à la collecte des déchets juste après sa dernière utilisation possible... avant qu'elle ne sorte réellement de la portée. Voir stackoverflow.com/questions/680550/explicit-nulling

2 votes

Le ramasseur d'ordures et la gigue ne fonctionnent pas de cette façon. La portée est une construction du langage et non quelque chose à laquelle le runtime obéira. En fait, vous allongez probablement la durée de présence de la référence en mémoire, en ajoutant un appel à .Dispose() à la fin du bloc. Voir ericlippert.com/2015/05/18/

9voto

Joe Points 60749

La réponse à cette question a déjà été donnée, mais j'ajouterai simplement que le bon vieux principe de la dissimulation de l'information signifie qu'il se peut qu'à un moment donné, vous souhaitiez procéder à une refonte :

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

à :

Stream foo()
{    
   ...
}

Cela souligne le fait que les appelants ne doivent pas se soucier du type de Stream renvoyé et permet de modifier l'implémentation interne (par exemple, lors de la création de simulateurs pour les tests unitaires).

Vous aurez alors besoin d'être en difficulté si vous n'avez pas utilisé Dispose dans votre implémentation de la barre :

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

5voto

Nick Points 5505

Tous les flux implémentent IDisposable. Enveloppez votre flux de mémoire dans une déclaration using et tout ira bien. Le bloc using garantit que votre flux est fermé et mis au rebut quoi qu'il arrive.

Chaque fois que vous appelez Foo, vous pouvez faire using(MemoryStream ms = foo()) et je pense que tout devrait bien se passer.

1 votes

Un problème que j'ai rencontré avec cette habitude est que vous devez être sûr que le flux n'est pas utilisé ailleurs. Par exemple, j'ai créé un JpegBitmapDecoder qui pointe vers un MemoryStream et renvoie Frames[0] (en pensant qu'il copierait les données dans sa propre mémoire interne) mais j'ai constaté que le bitmap n'apparaissait que 20 % du temps - il s'est avéré que c'était parce que je disposais du flux de mémoire.

0 votes

Si votre flux de mémoire doit persister (c'est-à-dire qu'un bloc using n'a pas de sens), alors vous devez appeler Dispose et mettre immédiatement la variable à null. Si vous l'éliminez, c'est qu'elle n'est plus destinée à être utilisée, et vous devez donc également lui attribuer la valeur null immédiatement. Ce que chaiguy décrit ressemble à un problème de gestion des ressources, parce que vous ne devriez pas distribuer une référence à quelque chose à moins que la chose à qui vous la donnez ne prenne la responsabilité de la disposer, et que la chose qui donne la référence sache qu'elle n'est plus responsable de le faire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X