web-dev-qa-db-fra.com

Comment renvoyer un Stream à partir d'une méthode, sachant qu'il doit être supprimé?

J'ai une méthode qui prend FileStream en entrée. Cette méthode s'exécute dans une boucle for.

private void UploadFile(FileStream fileStream)
{
    var stream = GetFileStream();
    // do things with stream
}

J'ai une autre méthode qui crée et renvoie le FileStream:

private FileStream GetFileStream()
{
    using(FileStream fileStream = File.Open(myFile, FileMode.Open))
    {
        //Do something
        return fileStream;
    }
}

Maintenant, la première méthode lance un ObjectDisposedException lorsque j'essaie d'accéder au FileStream retourné, probablement parce qu'il est déjà fermé car j'utilise "using" pour éliminer correctement le flux.

Si je n'utilise pas "using" et que je l'utilise à la place comme suit, le FileStream reste ouvert et l'itération suivante de la boucle (opérant sur le même fichier) lève une exception indiquant que le fichier est déjà utilisé:

private FileStream GetFileStream()
{
    FileStream fileStream = File.Open(myFile, FileMode.Open);
    //Do something
    return fileStream;
}

Si j'utilise un bloc try-finally, où je ferme le flux dans le finally, il jette également le ObjectDisposedException.

Comment retourner efficacement un flux de fichiers et le fermer?

22
Frank Martin

Lorsque vous renvoyez un IDisposable à partir d'une méthode, vous reléguez la responsabilité de le supprimer à votre appelant. Ainsi, vous devez déclarer votre bloc using autour de l'utilisation entière du flux, qui dans votre cas s'étend probablement sur l'appel UploadFile.

using (var s = GetFileStream())
    UploadFile(s);
29
Douglas

Le problème est que l'objet FileStream est supprimé dès que vous quittez la méthode GetFileStream(), le laissant dans un état inutilisable. Comme d'autres réponses l'indiquent déjà, vous devez supprimer le bloc using de cette méthode et placer à la place le bloc using autour de tout code qui appelle cette méthode:

private FileStream GetFileStream()
{
    FileStream fileStream = File.Open(myFile, FileMode.Open);
    //Do something
    return fileStream;
}

using (var stream = GetFileStream())
{
    UploadFile(stream);
}

Cependant, je veux aller un peu plus loin. Vous voulez un moyen de protéger le flux créé par votre GetFileStream() du cas où un programmeur bâclé pourrait appeler la méthode sans un bloc using, ou au moins indiquer d'une manière ou d'une autre fortement aux appelants que le résultat de cette méthode doit être entourée d'un bloc using. Par conséquent, je recommande ceci:

public class FileIO : IDisposable
{
    private FileStream streamResult = null;

    public FileStream CreateFileStream(string myFile)
    {
        streamResult = File.Open(myFile, FileMode.Open);
        //Do something
        return streamResult;
    }

    public void Dispose()
    { 
       if (streamResult != null) streamResult.Dispose();         
    }

}

using (var io = FileIO())
{
    var stream = io.CreateFileStream(myFile);

    // loop goes here.
}

Notez que vous n'avez pas nécessairement besoin de créer une toute nouvelle classe pour cela. Vous pouvez déjà avoir une classe appropriée pour cette méthode où vous pouvez simplement ajouter le code IDisposable. L'essentiel est que vous pouvez utiliser IDisposable comme signal aux autres programmeurs que ce code doit être encapsulé avec un bloc using.

De plus, cela vous permet de modifier la classe afin que vous puissiez créer votre objet IDisposable une fois, avant la boucle, et que la nouvelle instance de classe garde une trace de tout ce dont vous avez besoin à la fin de la boucle.

12
Joel Coehoorn

Si vous disposez d'une méthode qui doit renvoyer un flux de fichiers ouvert, tous les appelants de cette méthode doivent prendre la responsabilité de supprimer le flux renvoyé, car ils ne peuvent pas supprimer le flux avant de le renvoyer.

5
Servy