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;
}
Preocupante binário
fonte
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á.
Jon Skeet
fonte
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
)
Maliger
fonte
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.

Darin Dimitrov
fonte
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;
}
tudor
fonte
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);
    }
}
Aaron Murgatroyd
fonte
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