2008-11-13 58 views
20

Estoy probando cómo las clases FileStream y StreamReader funcionan juntas. A través de una aplicación de consola. Estoy tratando de entrar en un archivo y leer las líneas e imprimirlas en la consola.Problema de FileStream StreamReader en C#

He podido hacerlo con un ciclo while, pero quiero probarlo con un ciclo foreach.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 

namespace testing 
{ 
    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      string file = @"C:\Temp\New Folder\New Text Document.txt"; 
      using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read)) 
      { 
       using(StreamReader sr = new StreamReader(fs)) 
       { 
        foreach(string line in file) 
        { 
         Console.WriteLine(line); 
        } 
       } 
      } 
     } 
    } 
} 

El error me siguen dando para esto es: no se puede convertir el tipo char 'a 'cadena'

El bucle while, que hace el trabajo, tiene el siguiente aspecto:

while((line = sr.ReadLine()) != null) 
{ 
    Console.WriteLine(line); 
} 

I Probablemente esté pasando por alto algo realmente básico, pero no puedo verlo.

+0

Sobre el tema de foreach (relacionado directamente con su comentario sobre el "rendimiento"), recomiendo el capítulo 6 libre de C# en profundidad - aquí: http://www.manning.com/skeet/ –

+0

Su problema es que su código se está iterando (foreach) a través de cada elemento en "archivo" (que es una cadena). Por lo tanto, cada elemento es un "char". De ahí el mensaje de error del compilador que está tratando de convertir al tipo de cadena. Debe iterar (foreach) a través de los datos de la secuencia. – RichS

Respuesta

22

Para leer todas las líneas de texto nuevo Document.txt:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 

namespace testing 
{ 
    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      string file = @"C:\Temp\New Folder\New Text Document.txt"; 
      using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read)) 
      {      
       using(StreamReader sr = new StreamReader(fs)) 
       { 
        while(!sr.EndOfStream) 
        { 
         Console.WriteLine(sr.ReadLine()); 
        } 
       } 
      } 
     } 
    } 
} 
3

El problema está en:

foreach(string line in file) 
{ 
    Console.WriteLine(line); 
} 

Su debido a que el "archivo" es una cadena, y la cadena implementa IEnumerable. Pero este enumerador devuelve "char" y "char" no se puede convertir implícitamente a cadena.

Debe usar el ciclo while, como dirá.

0

Está enumerando una cadena, y cuando lo hace, toma una char en ese momento.

¿Estás seguro de que esto es lo que quieres?

foreach(string line in file) 
+0

No es realmente lo que quiero, solo estoy probando las posibilidades. Nunca he usado el foreach-loop realmente. – Vordreller

1

Parece que la tarea a mí;)

Usted está interactuando sobre el nombre del archivo (una cadena) sí que le da un carácter a la vez. Simplemente use el enfoque while que usa correctamente sReReignLine().

+0

Es 'un poco' tarea. Estoy trabajando en otra cosa que contiene cosas similares. Así que estoy aprendiendo un poco las clases FileStream y StreamReader/StreamWriter ^^ – Vordreller

1

En lugar de utilizar un StreamReader y luego tratando de encontrar líneas dentro de la variable String file, puede simplemente usar File.ReadAllLines:

string[] lines = File.ReadAllLines(file); 
foreach(string line in lines) 
    Console.WriteLine(line); 
0

Un enfoque simplista (no eficiente de la memoria) de la iteración cada línea en un archivo es

foreach (string line in File.ReadAllLines(file)) 
{ 
    .. 
} 
+0

Ver mi publicación para una versión que no es de almacenamiento en memoria intermedia de este –

35

Si desea leer un archivo línea por línea a través de foreach (de una forma reutilizable), considere el siguiente bloque de iterador:

public static IEnumerable<string> ReadLines(string path) 
    { 
     using (StreamReader reader = File.OpenText(path)) 
     { 
      string line; 
      while ((line = reader.ReadLine()) != null) 
      { 
       yield return line; 
      } 
     } 
    } 

Tenga en cuenta que esto se evalúa de forma perezosa: no hay ninguno de los almacenamientos intermedios que asociaría con File.ReadAllLines(). La sintaxis foreach se asegurará de que el iterador es Dispose() d correctamente incluso para las excepciones, cerrando el archivo:

foreach(string line in ReadLines(file)) 
{ 
    Console.WriteLine(line); 
} 

(se añade este bit sólo por interés ...)

Otra ventaja de este tipo de abstracción es que juega muy bien con LINQ - es decires fácil de hacer transformaciones/filtros, etc con este enfoque:

 DateTime minDate = new DateTime(2000,1,1); 
     var query = from line in ReadLines(file) 
        let tokens = line.Split('\t') 
        let person = new 
        { 
         Forname = tokens[0], 
         Surname = tokens[1], 
         DoB = DateTime.Parse(tokens[2]) 
        } 
        where person.DoB >= minDate 
        select person; 
     foreach (var person in query) 
     { 
      Console.WriteLine("{0}, {1}: born {2}", 
       person.Surname, person.Forname, person.DoB); 
     } 

Y de nuevo, todo evaluado con pereza (sin almacenamiento temporal).

+0

Desde .NET 4.0 esto está integrado en el BCL como ['File.ReadLines'] (http://msdn.microsoft.com/en-us/library/dd383503 (v = vs.100) .aspx) – CodesInChaos

+0

no usaría 'while (! reader.EndOfStream) {yield return reader.ReadLine()}' ¿sería ligeramente mejor en cuanto a rendimiento? Obviamente, no por un margen largo en absoluto, pero la variable 'línea' anterior se usa en ninguna otra parte que no sea en 'rendimiento devuelto', y personalmente encuentro la condición más legible de esta manera. – cogumel0

0

supongo que usted quiere algo como esto:

using (FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) 
{ 
    using (StreamReader streamReader = new StreamReader(fileStream)) 
    { 
     string line = ""; 
     while (null != (line = streamReader.ReadLine())) 
     { 
      Console.WriteLine(line); 
     } 
    } 
} 
+1

Eso es exactamente lo que imprimí en mi primera publicación, solo un nombre de puntero diferente para el StreamReader y el! = Null en el otro lado del ciclo while ... – Vordreller

+0

Bueno, no del todo, ya que la mía funciona, la tuya no. ;-) Su problema es que está haciendo un "foreach" en el nombre del archivo, no en la transmisión. – RichS

22

tengo una clase LineReader en mi proyecto MiscUtil. Es un poco más general que las soluciones que se dan aquí, sobre todo en términos de la forma en que puede construirlo:

  • De una función que devuelve una corriente, en cuyo caso se utilizará UTF-8
  • De una función que devuelve una corriente, y una codificación
  • partir de una función que devuelve un lector de texto
  • a partir de sólo un nombre de archivo, en cuyo caso se utilizará UTF-8
  • Desde un nombre de archivo y una codificación

La clase "posee" los recursos que utiliza y los cierra de forma adecuada. Sin embargo, lo hace sin implementar IDisposable. Esta es la razón por la que se necesitan Func<Stream> y Func<TextReader> en lugar de la transmisión o el lector directamente; es necesario que pueda diferir la apertura hasta que lo necesite. Es el propio iterador (que se elimina automáticamente mediante un bucle foreach) que cierra el recurso.

Como Marc señaló, esto funciona muy bien en LINQ. Un ejemplo me gusta dar es:

var errors = from file in Directory.GetFiles(logDirectory, "*.log") 
      from line in new LineReader(file) 
      select new LogEntry(line) into entry 
      where entry.Severity == Severity.Error 
      select entry; 

Esto transmitir todos los errores de un montón de archivos de registro, abriendo y cerrando a medida que avanza. Combinado con Push LINQ, puede hacer todo tipo de cosas agradables :)

No es una clase particularmente "difícil", pero es realmente útil. Aquí está la fuente completa, por conveniencia, si no desea descargar MiscUtil. La licencia del código fuente es here.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.IO; 
using System.Text; 

namespace MiscUtil.IO 
{ 
    /// <summary> 
    /// Reads a data source line by line. The source can be a file, a stream, 
    /// or a text reader. In any case, the source is only opened when the 
    /// enumerator is fetched, and is closed when the iterator is disposed. 
    /// </summary> 
    public sealed class LineReader : IEnumerable<string> 
    { 
     /// <summary> 
     /// Means of creating a TextReader to read from. 
     /// </summary> 
     readonly Func<TextReader> dataSource; 

     /// <summary> 
     /// Creates a LineReader from a stream source. The delegate is only 
     /// called when the enumerator is fetched. UTF-8 is used to decode 
     /// the stream into text. 
     /// </summary> 
     /// <param name="streamSource">Data source</param> 
     public LineReader(Func<Stream> streamSource) 
      : this(streamSource, Encoding.UTF8) 
     { 
     } 

     /// <summary> 
     /// Creates a LineReader from a stream source. The delegate is only 
     /// called when the enumerator is fetched. 
     /// </summary> 
     /// <param name="streamSource">Data source</param> 
     /// <param name="encoding">Encoding to use to decode the stream 
     /// into text</param> 
     public LineReader(Func<Stream> streamSource, Encoding encoding) 
      : this(() => new StreamReader(streamSource(), encoding)) 
     { 
     } 

     /// <summary> 
     /// Creates a LineReader from a filename. The file is only opened 
     /// (or even checked for existence) when the enumerator is fetched. 
     /// UTF8 is used to decode the file into text. 
     /// </summary> 
     /// <param name="filename">File to read from</param> 
     public LineReader(string filename) 
      : this(filename, Encoding.UTF8) 
     { 
     } 

     /// <summary> 
     /// Creates a LineReader from a filename. The file is only opened 
     /// (or even checked for existence) when the enumerator is fetched. 
     /// </summary> 
     /// <param name="filename">File to read from</param> 
     /// <param name="encoding">Encoding to use to decode the file 
     /// into text</param> 
     public LineReader(string filename, Encoding encoding) 
      : this(() => new StreamReader(filename, encoding)) 
     { 
     } 

     /// <summary> 
     /// Creates a LineReader from a TextReader source. The delegate 
     /// is only called when the enumerator is fetched 
     /// </summary> 
     /// <param name="dataSource">Data source</param> 
     public LineReader(Func<TextReader> dataSource) 
     { 
      this.dataSource = dataSource; 
     } 

     /// <summary> 
     /// Enumerates the data source line by line. 
     /// </summary> 
     public IEnumerator<string> GetEnumerator() 
     { 
      using (TextReader reader = dataSource()) 
      { 
       string line; 
       while ((line = reader.ReadLine()) != null) 
       { 
        yield return line; 
       } 
      } 
     } 

     /// <summary> 
     /// Enumerates the data source line by line. 
     /// </summary> 
     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return GetEnumerator(); 
     } 
    } 
} 
2

Un poco más elegante es la siguiente ...

using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) 
{ 
    using (var streamReader = new StreamReader(fileStream)) 
    { 
     while (!streamReader.EndOfStream) 
     { 
      yield return reader.ReadLine(); 
     } 
    } 
} 
Cuestiones relacionadas