2010-12-05 58 views
9

Consultez le code suivant:Pourquoi un flux dispose-t-il lorsque son éditeur est éliminé?

using (var ms = new MemoryStream()) 
{ 
    using(var writer = BinaryWriter(ms)) 
    { 
     writer.Write(/*something*/); 
     writer.Flush(); 
    } 

    Assert.That(ms.Length > 0); // Throws ObjectDisposedException 
} 

D'une part, un objet jetable devrait disposer des ressources de elle; Je comprends cela, mais d'un autre côté, l'objet n'a pas créé et ne possède pas cette ressource, il a été fourni -> le code appelant devrait en assumer la responsabilité ... non?

Je ne peux pas penser à d'autres situations comme celle-ci, mais est-ce un modèle cohérent dans le cadre pour toute classe recevant des objets jetables à disposer d'eux-mêmes?

Répondre

13

Il existe une supposition implicite que vous n'aurez qu'un seul auteur par flux, donc l'auteur assume la propriété du flux pour plus de commodité - vous avez alors une chose à nettoyer.

Mais je suis d'accord; ce n'est pas toujours vrai et souvent gênant. Certaines implémentations (DeflateStream, GZipStream, par exemple) vous permettent de choisir. Sinon, la seule option réelle consiste à injecter un flux fictif entre l'auteur et le flux sous-jacent; IIRC il y a un NonClosingStreamWrapper dans la bibliothèque « MiscUtil » de Jon Skeet qui fait exactement ceci: http://www.yoda.arachsys.com/csharp/miscutil/

utilisation serait quelque chose comme:

using (var ms = new MemoryStream()) 
{ 
    using(var noClose = new NonClosingStreamWrapper(ms)) 
    using(var writer = BinaryWriter(noClose)) 
    { 
        writer.Write(/*something*/); 
        writer.Flush(); 
    } 

    Assert.That(ms.Length > 0); 
} 
4

Je suis entièrement d'accord avec vous. Ce comportement n'est pas cohérent, mais c'est comme cela qu'il a été mis en œuvre. Il y a comments à la fin de la documentation sur ce comportement qui n'est pas très intuitif. Tous les auteurs de flux prennent en charge le flux sous-jacent et l'éliminent. Personnellement, je toujours nid ma déclaration using comme ceci:

using (var ms = new MemoryStream()) 
using(var writer = BinaryWriter(ms)) 
{ 
    writer.Write(/*something*/); 
} 

de sorte qu'un code comme celui que vous mettez dans le Assertion ne doit pas être écrit.

0

La bonne chose à avoir fait aurait été d'avoir un paramètre constructeur pour le streamwriter indique si le flux doit être éliminé lorsque le constructeur est. Étant donné que Microsoft n'a pas fait cela, il peut être bon de définir une classe NonDisposingStream (Of T as Stream) qui encapsule un flux mais ne passe pas un appel Dispose au flux encapsulé. On pourrait alors passer un nouveau NonDisposingStream au constructeur d'un StreamWriter, et le flux sous-jacent serait à l'abri de la disposition (il faudrait, bien sûr, se débarrasser du flux). Avoir un objet qui peut disposer d'un objet transféré est utile. Bien qu'un tel comportement ne coïncide pas avec le modèle habituel du créateur d'un objet manipulant sa disposition, il y a souvent des situations où le créateur d'un objet n'aura aucune idée de combien de temps l'objet sera réellement nécessaire. Par exemple, on peut s'attendre à ce qu'une méthode crée un nouveau StreamWriter qui utilise un nouveau Stream. Le propriétaire du StreamWriter saura quand il doit être éliminé, mais peut ne pas connaître l'existence du flux interne. Le créateur du flux interne n'aura aucune idée de la durée d'utilisation du StreamWriter externe. Avoir la propriété du flux «transféré» au StreamWriter résout le problème d'élimination dans ce cas particulier (commun).

0

Je propose cette classe d'emballage:

public class BetterStreamWriter : StreamWriter 
{ 
    private readonly bool _itShouldDisposeStream; 

    public BetterStreamWriter(string filepath) 
     :base(filepath) 
    { 
     _itShouldDisposeStream = true; 
    } 

    public BetterStreamWriter(Stream stream) 
     : base(stream) 
    { 
     _itShouldDisposeStream = false; 
    } 

    protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing && _itShouldDisposeStream); 
    } 
} 

objets ne doivent pas disposer des choses qu'ils n'instancier. Si c'est un écrivain de flux de fichiers, il doit disposer. Si c'est un flux externe, il ne devrait pas.

N'aurait pas dû implémenter le chemin du fichier d'ouverture en premier lieu.Cela viole le principe de la responsabilité unique puisque l'objet gère à la fois l'écriture et la durée de vie du fichier.

+1

Les objets ne doivent pas disposer de choses qu'ils ne possèdent pas *. Il est possible d'acquérir la propriété d'objets autrement qu'en les instanciant directement (le plus souvent en appelant une méthode usine). Étant donné que les méthodes d'usine ne renvoient généralement qu'un seul objet, il est nécessaire que toutes les ressources acquises par la méthode appartiennent à cet objet. Avoir un 'StreamWriter' s'approprier un flux transmis permet à une méthode d'usine de construire légitimement un flux et de renvoyer un' StreamWriter' qui l'encapsule. – supercat

+1

@supercat hein no. Je suis d'accord avec vous sur "Les objets ne doivent pas disposer de choses qu'ils ne possèdent pas". C'est la bonne façon de le dire. Cependant, pas dans ce cas. En concevant la classe StreamWriter elle-même, vous n'avez aucune idée de la façon dont l'appelant gère le flux, et il ne devrait pas non plus le faire. Considérez les cas où un flux est utilisé dans plusieurs lecteurs. Ensuite, vous devez conditionner le StreamReader conditionnellement à l'aide d'instruction en fonction de si vous souhaitez le disposer? IDisposable devrait être warpped en utilisant l'instruction. Pas de questions posées. –

+0

L'approche appropriée est pour le constructeur de 'StreamReader' /' StreamWriter' pour permettre à l'appelant de spécifier si la propriété est transférée (ce qu'ils font dans les versions ultérieures de .NET). Sinon, considérez comment on écrirait une méthode supposée lire de manière asynchrone une donnée audio récupérée à partir d'un StreamReader. Le code qui lit l'audio peut ne rien savoir sur le flux sous-jacent, et le code qui construit le 'StreamReader' peut n'avoir aucune idée quand le code de lecture est fait avec lui. Dans le cas courant où le flux a été ouvert uniquement dans le but de la lecture audio ... – supercat