Existe alguma maneira de fechar um StreamWriter sem fechar seu BaseStream?


117

Meu problema raiz é que quando usingchama Disposeum StreamWriter, ele também descarta o BaseStream(mesmo problema com Close).

Eu tenho uma solução alternativa para isso, mas como você pode ver, envolve copiar o fluxo. Existe alguma maneira de fazer isso sem copiar o fluxo?

O objetivo disso é obter o conteúdo de uma string (originalmente lida de um banco de dados) em um fluxo, para que o fluxo possa ser lido por um componente de terceiros.
NB : Não consigo alterar o componente de terceiros.

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;
}

Usado como

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

Idealmente, estou procurando um método imaginário chamado BreakAssociationWithBaseStream, por exemplo,

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;
}

Esta é uma pergunta semelhante: stackoverflow.com/questions/2620851
Jens Granlund

Eu estava fazendo isso com um stream de um WebRequest, curiosamente, você pode fechá-lo se a codificação for ASCII, mas não UTF8. Esquisito.
tofutim

tofutim, eu codifiquei o meu como ASCII, e ele ainda descarta o fluxo subjacente ..
Gerard ONeill

Respostas:


121

Se você estiver usando o .NET Framework 4.5 ou posterior, há uma sobrecarga do StreamWriter com a qual você pode solicitar que o fluxo de base seja deixado aberto quando o gravador for fechado .

Em versões anteriores do .NET Framework anteriores a 4.5, StreamWriter assume que é o proprietário do fluxo. Opções:

  • Não descarte o StreamWriter; apenas dê descarga.
  • Crie um wrapper de fluxo que ignora chamadas para Close/ Disposemas faz proxy de todo o resto. Eu tenho uma implementação disso em MiscUtil , se você quiser pegá-la de lá.

15
Claramente, a sobrecarga de 4,5 foi uma concessão não pensada - a sobrecarga requer o tamanho do buffer, que não pode ser 0 nem nulo. Internamente, sei que 128 caracteres é o tamanho mínimo, então eu apenas defini como 1. Caso contrário, esse 'recurso' me deixa feliz.
Gerard ONeill de

Existe uma maneira de definir esse leaveOpenparâmetro depois de StreamWritercriado?
c00000fd

@ c00000fd: Não que eu saiba.
Jon Skeet,

1
@Yepeekai: "se eu passar um stream para um submétodo e esse submétodo criar o StreamWriter, ele será descartado no final da execução desse submétodo" Não, isso simplesmente não é verdade. Só será eliminado se algo o solicitar Dispose. A finalização do método não faz isso automaticamente. Ele pode ser finalizado mais tarde se tiver um finalizador, mas não é a mesma coisa - e ainda não está claro qual perigo você está prevendo. Se você acha que não é seguro retornar um StreamWriterde um método porque ele poderia ser descartado automaticamente pelo GC, isso simplesmente não é verdade.
Jon Skeet

1
@Yepeekai: E o IIRC StreamWriternão tem um finalizador - eu não esperava, justamente por esse motivo.
Jon Skeet

44

.NET 4.5 tem um novo método para isso!

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
)

Obrigado cara! Não sabia disso e, no mínimo, seria um bom motivo para eu começar a mirar no .NET 4.5!
Vectovox de

22
É uma pena que não haja sobrecarga que não exija que bufferSize seja definido. Estou feliz com o padrão lá. Estou tendo que passar sozinho. Não é o fim do mundo.
Andy McCluggage

3
O padrão bufferSizeé 1024. Os detalhes estão aqui .
Alex Klaus

35

Simplesmente não ligue Disposepara o StreamWriter. A razão pela qual essa classe é descartável não é porque ela contém recursos não gerenciados, mas para permitir o descarte do fluxo que poderia conter recursos não gerenciados. Se a vida do fluxo subjacente for tratada em outro lugar, não há necessidade de descartar o gravador.


2
@Marc, chamar não Flushfaria o trabalho no caso de armazenar dados em buffer?
Darin Dimitrov,

3
Tudo bem, mas assim que sairmos do CreateStream, o StreamWrtier poderá ser colecionado, forçando o leitor da terceira parte a correr contra o GC, o que não é uma situação na qual eu gostaria de ficar. Ou estou perdendo alguma coisa?
Binary Worrier

9
@BinaryWorrier: Não, não há condição de corrida: StreamWriter não tem um finalizador (e de fato não deveria).
Jon Skeet

10
@Binary Worrier: você só deve ter um finalizador se for o proprietário direto do recurso. Nesse caso, o StreamWriter deve assumir que o Stream se limpará se for necessário.
Jon Skeet

2
Parece que o método 'close' de StreamWriter também fecha e descarta o fluxo. Portanto, deve-se limpar, mas não fechar ou descartar o riacho, para que ele não feche o riacho, o que faria o equivalente a descartar o riacho. Muita "ajuda" da API aqui.
Gerard ONeill de

5

O fluxo de memória tem uma propriedade ToArray que pode ser usada mesmo quando o fluxo está fechado. To Array grava o conteúdo do fluxo em uma matriz de bytes, independentemente da propriedade Position. Você pode criar um novo fluxo com base no fluxo que você escreveu.

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;
}

Isso limita corretamente a matriz retornada ao tamanho do conteúdo? Porque nãoStream.Position pode ser chamado depois de eliminado.
Nyerguds

2

Você precisa criar um descendente do StreamWriter e substituir seu método dispose, sempre passando false para o parâmetro disposing, isso forçará o gravador de stream a NÃO fechar, o StreamWriter apenas chama dispose no método close, então não há necessidade de substituí-lo (é claro que você pode adicionar todos os construtores se quiser, eu só tenho um):

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

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

3
Eu acredito que isso não faz o que você pensa. A disposingbandeira é parte do IDisposablepadrão . Sempre passar falsepara o Dispose(bool)método da classe base basicamente sinaliza para o StreamWriterque ele está sendo chamado do finalizador (o que não é assim quando você chama Dispose()explicitamente) e, portanto, não deve acessar nenhum objeto gerenciado. Este é por isso que ele não vai descartar o fluxo de base. No entanto, a maneira como você conseguiu isso é um hack; seria muito mais simples simplesmente não ligar Disposeem primeiro lugar!
stakx - não contribuindo mais em

Isso é realmente da Symantec, qualquer coisa que você fizer além de reescrever o streaming inteiramente do zero será um hack. Definitivamente, você simplesmente não poderia chamar base.Dispose (false) de forma alguma, mas não haveria nenhuma diferença funcional, e eu gosto da clareza do meu exemplo. No entanto, tenha isso em mente, uma versão futura da classe StreamWriter pode fazer mais do que apenas fechar o fluxo ao descartá-lo, portanto, chamar dispose (false) prova o futuro também. Mas para cada um com o seu.
Aaron Murgatroyd

2
Outra maneira de fazer isso seria criar seu próprio wrapper de fluxo que contém outro fluxo onde o método Close simplesmente não faz nada em vez de fechar o fluxo subjacente, isso é menos um hack, mas é mais trabalhoso.
Aaron Murgatroyd

Sincronismo incrível: eu estava prestes a sugerir a mesma coisa (classe decorador, possivelmente chamada OwnedStream, que ignora Dispose(bool)e Close).
stakx - não contribui mais em

Sim, o código acima é como eu faço para um método rápido e sujo, mas se eu estivesse fazendo um aplicativo comercial ou algo que realmente importasse para mim, eu faria corretamente usando a classe de wrapper Stream. Pessoalmente, acho que a Microsoft cometeu um erro aqui, o streamwriter deveria ter uma propriedade booleana para fechar o stream subjacente, mas eu não trabalho na Microsoft, então eles fazem o que gostam, eu acho: D
Aaron Murgatroyd
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.