¿Hay alguna forma de cerrar StreamWriter sin cerrar su BaseStream?

117

Mi problema principal es que cuando usingllama Disposea a StreamWriter, también elimina el BaseStream(mismo problema con Close).

Tengo una solución para esto, pero como puede ver, implica copiar la transmisión. ¿Hay alguna forma de hacer esto sin copiar la transmisión?

El propósito de esto es obtener el contenido de una cadena (originalmente leída de una base de datos) en una secuencia, para que la secuencia pueda ser leída por un componente de terceros.
NB : No puedo cambiar el componente de terceros.

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, estoy buscando un método imaginario llamado BreakAssociationWithBaseStream, por ejemplo

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;
}
Preocupado binario
fuente
Esta es una pregunta similar: stackoverflow.com/questions/2620851
Jens Granlund
Estaba haciendo esto con un flujo de WebRequest, curiosamente, puede cerrarlo si la codificación es ASCII pero no UTF8. Extraño.
tofutim
tofutim, tenía el mío codificado como ASCII, y todavía elimina el flujo subyacente ..
Gerard ONeill

Respuestas:

121

Si está utilizando .NET Framework 4.5 o posterior, existe una sobrecarga de StreamWriter mediante la cual puede solicitar que la secuencia base se deje abierta cuando se cierre el escritor .

En versiones anteriores de .NET Framework anteriores a la 4.5, se StreamWriter asume que posee la transmisión. Opciones:

  • No deseche el StreamWriter; simplemente enjuague.
  • Cree un contenedor de flujo que ignore las llamadas a Close/ Disposepero haga proxy de todo lo demás. Tengo una implementación de eso en MiscUtil , si quieres tomarlo desde allí.
Jon Skeet
fuente
15
Claramente, la sobrecarga de 4.5 fue una concesión no pensada: la sobrecarga requiere el tamaño del búfer, que no puede ser 0 ni nulo. Internamente, sé que 128 caracteres es el tamaño mínimo, así que lo configuré en 1. De lo contrario, esta 'característica' me hace feliz.
Gerard ONeill
¿Hay alguna forma de establecer ese leaveOpenparámetro después de que StreamWriterse creó?
c00000fd
@ c00000fd: No que yo sepa.
Jon Skeet
1
@Yepeekai: "si paso un flujo a un submétodo y ese submétodo crea el StreamWriter, se eliminará al final de la ejecución de ese submétodo" No, eso simplemente no es cierto. Solo se eliminará si algo lo solicita Dispose. La terminación del método no hace eso automáticamente. Es posible que se finalice más tarde si tiene un finalizador, pero eso no es lo mismo, y aún no está claro qué peligro está anticipando. Si cree que no es seguro devolver un StreamWriterde un método porque el GC podría eliminarlo automáticamente, eso no es cierto.
Jon Skeet
1
@Yepeekai: Y el IIRC StreamWriterno tiene un finalizador, no esperaría que lo tuviera, precisamente por esta razón.
Jon Skeet
44

¡.NET 4.5 tiene un nuevo método para eso!

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
fuente
¡Gracias amigo! No sabía esto, y en todo caso, ¡sería una buena razón para comenzar a apuntar a .NET 4.5!
Vectovox
22
Es una pena que no haya sobrecarga que no requiera que se establezca bufferSize. Estoy contento con el valor predeterminado allí. Tengo que pasarlo yo mismo. No es el fin del mundo.
Andy McCluggage
3
El predeterminado bufferSizees 1024. Los detalles están aquí .
Alex Klaus
35

Simplemente no llame Disposeal StreamWriter. La razón por la que esta clase es desechable no es porque contenga recursos no administrados, sino para permitir la eliminación de la secuencia que en sí misma podría contener recursos no administrados. Si la vida de la secuencia subyacente se maneja en otro lugar, no es necesario deshacerse del escritor.

Darin Dimitrov
fuente
2
@Marc, ¿no Flushharía el trabajo llamar en caso de que almacenara datos?
Darin Dimitrov
3
Bien, pero una vez que salimos de CreateStream, el StreamWrtier es coleccionable, lo que obliga al lector de la tercera parte a competir contra el GC, que no es una situación en la que quiero quedarme. ¿O me estoy perdiendo algo?
Binary Worrier
9
@BinaryWorrier: No, no hay condición de carrera: StreamWriter no tiene un finalizador (y de hecho no debería).
Jon Skeet
10
@Binary Worrier: solo debes tener un finalizador si eres el propietario directo del recurso. En este caso, StreamWriter debe asumir que Stream se limpiará solo si es necesario.
Jon Skeet
2
Parece que el método 'cerrar' de StreamWriter también cierra y elimina la secuencia. Por lo tanto, uno debe vaciar, pero no cerrar o eliminar el streamwriter, para que no cierre el flujo, lo que haría el equivalente a eliminar el flujo. Demasiada "ayuda" de la API aquí.
Gerard ONeill
5

El flujo de memoria tiene una propiedad ToArray que se puede usar incluso cuando el flujo está cerrado. To Array escribe el contenido de la secuencia en una matriz de bytes, independientemente de la propiedad Position. Puede crear una nueva transmisión basada en la transmisión en la que escribió.

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
fuente
¿Limita eso correctamente la matriz devuelta al tamaño del contenido? Debido a que Stream.Positionpuede no ser llamado después de que se dispone.
Nyerguds
2

Necesita crear un descendiente de StreamWriter y anular su método de disposición, pasando siempre falso al parámetro de disposición, obligará al escritor de flujo a NO cerrarse, StreamWriter solo llama a dispose en el método de cierre, por lo que no es necesario anúlelo (por supuesto, puede agregar todos los constructores si lo desea, solo tengo uno):

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

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}
Aaron Murgatroyd
fuente
3
Creo que esto no hace lo que crees que hace. La disposingbandera es parte de la IDisposablepatrón . Pasar siempre falseal Dispose(bool)método de la clase base básicamente le indica StreamWriterque se está llamando desde el finalizador (que no es así cuando se llama Dispose()explícitamente) y, por lo tanto, no debería acceder a ningún objeto administrado. Esto es por lo que no se deshará la corriente de base. Sin embargo, la forma en que lo ha logrado es un truco; ¡Sería mucho más sencillo simplemente no llamar Disposeen primer lugar!
stakx - ya no contribuye el
Eso es realmente de Symantec, cualquier cosa que haga, aparte de reescribir la transmisión completamente desde cero, será un truco. Sin embargo, definitivamente, simplemente no podría llamar a base.Dispose (false) en absoluto, pero no habría diferencia funcional, y me gusta la claridad de mi ejemplo. Sin embargo, tenga esto en cuenta, una versión futura de la clase StreamWriter puede hacer más que simplemente cerrar la secuencia cuando se desecha, por lo que llamar a dispose (false) también lo prueba en el futuro. Pero a cada cual lo suyo.
Aaron Murgatroyd
2
Otra forma de hacerlo sería crear su propio contenedor de flujo que contenga otro flujo en el que el método Close simplemente no haga nada en lugar de cerrar el flujo subyacente, esto es menos un truco pero es más trabajo.
Aaron Murgatroyd
Increíble momento: estaba a punto de sugerir lo mismo (clase decoradora, posiblemente nombrada OwnedStream, que ignora Dispose(bool)y Close).
stakx - ya no contribuye el
Sí, el código anterior es cómo lo hago para un método rápido y sucio, pero si estuviera creando una aplicación comercial o algo que realmente me importara, lo haría correctamente usando la clase contenedora Stream. Personalmente, creo que Microsoft cometió un error aquí, el streamwriter debería haber tenido una propiedad booleana para cerrar la transmisión subyacente en su lugar, pero luego no trabajo en Microsoft, así que ellos hacen lo que les gusta, supongo: D
Aaron Murgatroyd