2010-10-10 21 views
9

Necesito leer una secuencia dos veces, de principio a fin.¿Por qué desechar StreamReader hace que una secuencia sea ilegible?

Pero el siguiente código arroja una excepción de ObjectDisposedException: Cannot access a closed file.

string fileToReadPath = @"<path here>"; 
using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open)) 
{ 
    using (StreamReader reader = new StreamReader(fs)) 
    { 
     string text = reader.ReadToEnd(); 
     Console.WriteLine(text); 
    } 

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException thrown. 

    using (StreamReader reader = new StreamReader(fs)) 
    { 
     string text = reader.ReadToEnd(); 
     Console.WriteLine(text); 
    } 
} 

¿Por qué está sucediendo? ¿Qué está realmente dispuesto? ¿Y por qué la manipulación de StreamReader afecta la secuencia asociada de esta manera? ¿No es lógico esperar que una secuencia buscable se pueda leer varias veces, incluso por varios StreamReader s?

+1

Considere también usar System.IO.File.ReadAllText() en situaciones como estas. Es mas simple –

+0

@Dave Markle: tienes razón. Lo he puesto como un pequeño ejemplo. En realidad, en el código real, las transmisiones que trato pueden ser muy grandes, por lo que el primer lector las lee línea por línea, luego, la transmisión se copia por byte a otra transmisión. –

Respuesta

12

Esto sucede porque el StreamReader asume la "propiedad" de la transmisión. En otras palabras, se hace responsable de cerrar la secuencia fuente. Tan pronto como su programa llame al Dispose o Close (dejando el alcance de la declaración using en su caso), también eliminará la fuente de transmisión. Llamando fs.Dispose() en su caso. Entonces la secuencia de archivos está muerta después de salir del primer bloque using. Es un comportamiento consistente, todas las clases de flujo en .NET que envuelven a otra secuencia se comportan de esta manera.

Hay un constructor para StreamReader que permite decir que no posee posee la secuencia fuente. Sin embargo, no es accesible desde un programa .NET, el constructor es interno.

En este caso particular, resolvería el problema al no usar la using -statement para el StreamReader. Sin embargo, es un detalle bastante complicado de implementación. Seguramente hay una mejor solución disponible para usted, pero el código es demasiado sintético para proponer uno real.

7

El propósito de Dispose() es limpiar los recursos cuando haya terminado la transmisión. La razón por la que el lector impacta en la corriente se debe a que el lector solo está filtrando la secuencia y, por lo tanto, deshacerse del lector no tiene ningún significado, excepto en el contexto de encadenar la llamada al flujo fuente.

Para solucionar el código, sólo tiene que utilizar un lector todo el tiempo:

using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open)) 
using (StreamReader reader = new StreamReader(fs)) 
{ 
    string text = reader.ReadToEnd(); 
    Console.WriteLine(text); 

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException not thrown now 

    text = reader.ReadToEnd(); 
    Console.WriteLine(text); 
} 

Editado para hacer frente a los comentarios a continuación:

En la mayoría de las situaciones, no es necesario para acceder a la secuencia subyacente como lo haces en tu código (fs.Seek). En estos casos, el hecho de que StreamReader encadena su llamada al flujo subyacente le permite economizar en el código al no usar ninguna declaración usings para el flujo. Por ejemplo, el código se vería así:

using (StreamReader reader = new StreamReader(new FileStream(fileToReadPath, FileMode.Open))) 
{ 
    ... 
} 
+0

De hecho, estoy preguntando por qué Dispose() en el * reader * afecta a * stream *. No entiendo tu respuesta. ¿Significa que los lectores Eliminar tienen solo un propósito para limpiar los datos de la secuencia? Entonces, ¿por qué incluso 'StreamReader' implementa' IDisposable', si no hace nada * útil * (ya que la eliminación de la secuencia en sí misma hará todo el trabajo)? –

+1

@MainMa, el propósito de Dispose() es * siempre * para asegurarse de que todos los recursos asociados con el 'IDisposable' dado se limpian. Como el lector y la secuencia representan la misma entidad, deshacerse de uno tiene el mismo efecto que deshacerse del otro. –

2

Using define un alcance, fuera de la cual se dispondrá un objeto, por lo tanto la ObjectDisposedException. No puede acceder al contenido de StreamReader fuera de este bloque.

0

Dispose() en el padre Dispose() todos los flujos propios. Lamentablemente, las transmisiones no tienen el método Detach(), por lo que debe crear alguna solución aquí.

0

No sé por qué, pero puede dejar su StreamReader sin oposición. De esta forma, su transmisión subyacente no se eliminará, incluso cuando se recopile StreamReader.

+0

Por supuesto. Pero no es demasiado intuitivo no usar un 'StreamReader' para mí, y probablemente viole las reglas de FxCop. Por cierto, el problema en el origen de esta pregunta apareció cuando reformé y el código viejo donde los usos faltaban por completo. –

1

Estoy de acuerdo con su pregunta. El mayor problema con este efecto secundario intencional es cuando los desarrolladores no lo conocen y siguen ciegamente la "mejor práctica" de rodear un StreamReader con un using. Pero puede causar algunos realmente difícil de rastrear errores cuando se encuentra en la propiedad de un objeto de larga vida, la mejor (peor?) Ejemplo que he visto es

using (var sr = new StreamReader(HttpContext.Current.Request.InputStream)) 
{ 
    body = sr.ReadToEnd(); 
} 

El desarrollador no tenía idea de la InputStream es ahora manguera para cualquier lugar futuro que espera que esté allí.

Obviamente, una vez que conozca las partes internas, debe evitar el using y simplemente leer y restablecer la posición. Pero pensé que un principio central del diseño de API era evitar los efectos secundarios, especialmente no destruir los datos sobre los que está actuando. Nada inherente a una clase que supuestamente es un "lector" debe borrar los datos que lee cuando se hace "usar". La eliminación del lector debe liberar cualquier referencia a la secuencia, no borrar la secuencia en sí. Lo único que puedo pensar es que la elección tuvo que hacerse ya que el lector está alterando otro estado interno de la secuencia, como la posición del puntero de búsqueda, que ellos asumieron que si está envolviendo un uso alrededor de él intencionalmente va a estar hecho con todo. Por otro lado, al igual que en su ejemplo, si está creando un Stream, el flujo en sí estará en un using, pero si está leyendo un Stream que se creó fuera de su método inmediato, es presuntuoso del código de borrar los datos

lo que hago y digo a nuestros desarrolladores para hacer en casos Stream que el código de lectura no crea explícitamente es ...

// save position before reading 
long position = theStream.Position; 
theStream.Seek(0, SeekOrigin.Begin); 
// DO NOT put this StreamReader in a using, StreamReader.Dispose() clears the stream 
StreamReader sr = new StreamReader(theStream); 
string content = sr.ReadToEnd(); 
theStream.Seek(position, SeekOrigin.Begin); 

(lo siento, añade esto como una respuesta, no cabrían en un comentario, me gustaría más discusión sobre esta decisión de diseño del framework)

+2

Un mejor diseño para un StreamReader hubiera sido tener un argumento de constructor que especifique si debe tomar posesión de la secuencia. Hay muchas situaciones en las que el código puede abrir una secuencia, crear un lector para ella, entregarle al lector algo que leerá los datos en el futuro y luego no tener más uso para la transmisión. En esa situación, la transmisión debe eliminarse cuando el lector haya terminado con ella, pero el creador de la transmisión puede no tener idea de cuándo será. – supercat

Cuestiones relacionadas