Existe-t-il un moyen de fermer un StreamWriter sans fermer son BaseStream?

117

Mon problème fondamental est que lorsque vous usingappelez Disposea StreamWriter, il élimine également le BaseStream(même problème avec Close).

J'ai une solution de contournement pour cela, mais comme vous pouvez le voir, cela implique de copier le flux. Existe-t-il un moyen de le faire sans copier le flux?

Le but de ceci est d'obtenir le contenu d'une chaîne (initialement lue à partir d'une base de données) dans un flux, afin que le flux puisse être lu par un composant tiers.
NB : je ne peux pas modifier le composant tiers.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    baseCopy.Seek(0, System.IO.SeekOrigin.Begin);
    return baseCopy;
}

Utilisé comme

public void Noddy()
{
    System.IO.Stream myStream = CreateStream("The contents of this string are unimportant");
    My3rdPartyComponent.ReadFromStream(myStream);
}

Idéalement, je recherche une méthode imaginaire appelée BreakAssociationWithBaseStream, par exemple

public System.IO.Stream CreateStream_Alternate(string value)
{
    var baseStream = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        writer.BreakAssociationWithBaseStream();
    }
    return baseStream;
}
Binaire Worrier
la source
C'est une question similaire: stackoverflow.com/questions/2620851
Jens Granlund
Je faisais cela avec un flux d'une WebRequest, il est intéressant de noter que vous pouvez le fermer si l'encodage est ASCII mais pas UTF8. Bizarre.
tofutim
tofutim, j'avais le mien encodé en ASCII, et il dispose toujours du flux sous-jacent ..
Gerard ONeill

Réponses:

121

Si vous utilisez .NET Framework 4.5 ou version ultérieure, il existe une surcharge StreamWriter à l'aide de laquelle vous pouvez demander au flux de base de rester ouvert lorsque l'enregistreur est fermé .

Dans les versions antérieures de .NET Framework antérieures à 4.5, StreamWriter suppose qu'il possède le flux. Options:

  • Ne jetez pas le StreamWriter; rincez-le.
  • Créez un wrapper de flux qui ignore les appels à Close/ Disposemais met tout le reste en proxy. J'ai une implémentation de cela dans MiscUtil , si vous voulez le récupérer à partir de là.
Jon Skeet
la source
15
Clairement, la surcharge 4.5 était une concession irréfléchie - la surcharge nécessite la taille de la mémoire tampon, qui ne peut pas être 0 ni nulle. En interne, je sais que la taille minimale est de 128 caractères, donc je la mets simplement à 1. Sinon, cette «fonctionnalité» me fait plaisir.
Gerard ONeill
Existe-t-il un moyen de définir ce leaveOpenparamètre après StreamWritersa création?
c00000fd
@ c00000fd: Pas que je sache.
Jon Skeet
1
@Yepeekai: "si je passe un flux à une sous-méthode et que cette sous-méthode crée le StreamWriter, il sera supprimé à la fin de l'exécution de cette sous-méthode" Non, ce n'est tout simplement pas vrai. Il ne sera éliminé que si quelque chose l’appelle Dispose. La fin de la méthode ne le fait pas automatiquement. Il peut être finalisé plus tard s'il a un finaliseur, mais ce n'est pas la même chose - et on ne sait toujours pas quel danger vous prévoyez. Si vous pensez qu'il n'est pas sûr de renvoyer un à StreamWriterpartir d'une méthode car il pourrait être automatiquement supprimé par le GC, ce n'est tout simplement pas vrai.
Jon Skeet
1
@Yepeekai: Et l'IIRC StreamWritern'a pas de finaliseur - je ne m'y attendais pas, précisément pour cette raison.
Jon Skeet
44

.NET 4.5 a une nouvelle méthode pour cela!

http://msdn.microsoft.com/EN-US/library/gg712853(v=VS.110,d=hv.2).aspx

public StreamWriter(
    Stream stream,
    Encoding encoding,
    int bufferSize,
    bool leaveOpen
)
Maliger
la source
Merci mon pote! Je ne le savais pas, et ce serait une bonne raison pour moi de commencer à cibler .NET 4.5!
Vectovox
22
Dommage qu'il n'y ait pas de surcharge qui n'exige pas que bufferSize soit défini. Je suis satisfait de la valeur par défaut. Je dois le passer moi-même. Pas la fin du monde.
Andy McCluggage le
3
La valeur par défaut bufferSizeest 1024. Les détails sont ici .
Alex Klaus
35

N'appelez tout simplement pas Disposele StreamWriter. La raison pour laquelle cette classe est supprimable n'est pas parce qu'elle contient des ressources non gérées, mais pour permettre l'élimination du flux qui pourrait lui-même contenir des ressources non gérées. Si la durée de vie du flux sous-jacent est gérée ailleurs, inutile de supprimer le rédacteur.

Darin Dimitrov
la source
2
@Marc, n'appellerait-il pas Flushle travail au cas où il mettrait en mémoire tampon les données?
Darin Dimitrov
3
Bien, mais une fois que nous avons quitté CreateStream, le StreamWrtier est collectable, forçant le lecteur de troisième partie à courir contre le GC, ce qui n'est pas une situation dans laquelle je veux être laissé. Ou est-ce que je manque quelque chose?
Binary Worrier
9
@BinaryWorrier: Non, il n'y a pas de condition de concurrence: StreamWriter n'a pas de finaliseur (et ne devrait pas en effet).
Jon Skeet
10
@Binary Worrier: Vous ne devriez avoir un finaliseur que si vous possédez directement la ressource. Dans ce cas, StreamWriter doit supposer que le Stream se nettoiera de lui-même si nécessaire.
Jon Skeet
2
Il semble que la méthode «close» de StreamWriter ferme et supprime également le flux. Il faut donc vider, mais pas fermer ou supprimer le streamwriter, donc il ne ferme pas le flux, ce qui ferait l'équivalent de supprimer le flux. Beaucoup trop "d'aide" de l'API ici.
Gerard ONeill
5

Le flux de mémoire a une propriété ToArray qui peut être utilisée même lorsque le flux est fermé. To Array écrit le contenu du flux dans un tableau d'octets, quelle que soit la propriété Position. Vous pouvez créer un nouveau flux basé sur le flux dans lequel vous avez écrit.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    var returnStream = new System.IO.MemoryStream( baseCopy.ToArray());
    return returnStream;
}
Tudor
la source
Cela limite-t-il correctement le tableau renvoyé à la taille du contenu? Parce que neStream.Position peut pas être appelé après avoir été éliminé.
Nyerguds
2

Vous devez créer un descendant de StreamWriter et remplacer sa méthode dispose, en passant toujours false au paramètre disposing, cela forcera l'écrivain de flux à NE PAS se fermer, le StreamWriter appelle simplement dispose dans la méthode close, il n'est donc pas nécessaire de le remplacer (bien sûr, vous pouvez ajouter tous les constructeurs si vous le souhaitez, j'en ai juste un):

public class NoCloseStreamWriter : StreamWriter
{
    public NoCloseStreamWriter(Stream stream, Encoding encoding)
        : base(stream, encoding)
    {
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}
Aaron Murgatroyd
la source
3
Je pense que cela ne fait pas ce que vous pensez. Le disposingdrapeau est située dans le IDisposablemotif . Passer toujours falseà la Dispose(bool)méthode de la classe de base signale essentiellement à la StreamWriterqu'elle est appelée depuis le finaliseur (ce qui n'est pas le cas lorsque vous appelez Dispose()explicitement), et ne devrait donc accéder à aucun objet géré. C'est pourquoi il ne supprimera pas le flux de base. Cependant, la façon dont vous avez réalisé cela est un hack; il serait beaucoup plus simple de ne pas appeler Disposeen premier lieu!
stakx - ne contribue plus
C'est vraiment Symantec, tout ce que vous faites en dehors de la réécriture du streaming entièrement à partir de zéro va être un hack. Certainement cependant, vous ne pourriez tout simplement pas appeler base.Dispose (false) du tout, mais il n'y aurait aucune différence fonctionnelle, et j'aime la clarté de mon exemple. Cependant, gardez cela à l'esprit, une future version de la classe StreamWriter peut faire plus que simplement fermer le flux lorsqu'il se débarrasse, donc appeler dispose (false) future le prouve également. Mais a chacun le sien.
Aaron Murgatroyd
2
Une autre façon de le faire serait de créer votre propre wrapper de flux qui contient un autre flux où la méthode Close ne fait simplement rien au lieu de fermer le flux sous-jacent, c'est moins un hack mais c'est plus de travail.
Aaron Murgatroyd
Moment incroyable: j'étais sur le point de suggérer la même chose (classe de décorateur, peut-être nommée OwnedStream, qui ignore Dispose(bool)et Close).
stakx - ne contribue plus
Oui, le code ci-dessus est la façon dont je le fais pour une méthode rapide et sale, mais si je faisais une application commerciale ou quelque chose qui comptait réellement pour moi, je le ferais correctement en utilisant la classe wrapper Stream. Personnellement, je pense que Microsoft a fait une erreur ici, le streamwriter aurait dû avoir une propriété booléenne pour fermer le flux sous-jacent à la place, mais je ne travaille pas chez Microsoft alors ils font ce qu'ils aiment je suppose: D
Aaron Murgatroyd