Problema FileStream StreamReader en C#
(10)
Estoy probando el funcionamiento de las clases FileStream y StreamReader. A través de una aplicación de consola. Intento entrar en un archivo y leer las líneas e imprimirlas en la consola.
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 que sigo recibiendo es: No se puede convertir el tipo ''char'' en ''cadena''
El ciclo while, que funciona, se ve así:
while((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
Probablemente estoy pasando por alto algo realmente básico, pero no puedo verlo.
El problema está en:
foreach(string line in file)
{
Console.WriteLine(line);
}
Es porque 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.
Deberías usar el ciclo while, como dices.
En lugar de usar un StreamReader
y luego tratar de encontrar líneas dentro de la variable del String file
, simplemente puede usar File.ReadAllLines
:
string[] lines = File.ReadAllLines(file);
foreach(string line in lines)
Console.WriteLine(line);
Está enumerando una cadena, y cuando lo hace, toma un char en ese momento.
¿Estás seguro de que esto es lo que quieres?
foreach(string line in file)
Me parece una tarea;)
Estás iterando sobre el nombre del archivo (una cadena), lo que te da un carácter a la vez. Simplemente use el enfoque while que usa correctamente sReReignLine ().
Para leer todas las líneas en New Text 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());
}
}
}
}
}
}
Si desea leer un archivo línea por línea a través de foreach (de manera reutilizable), considere el siguiente bloque de iteradores:
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 File.ReadAllLines()
que asociaría con File.ReadAllLines()
. La sintaxis foreach
asegurará que el iterador sea Dispose()
d correctamente incluso para las excepciones, cerrando el archivo:
foreach(string line in ReadLines(file))
{
Console.WriteLine(line);
}
(este bit se agrega solo por interés ...)
Otra ventaja de este tipo de abstracción es que funciona muy bien con LINQ, es decir, es fácil 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, todos evaluados perezosamente (sin almacenamiento en búfer).
Supongo que quieres 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 );
}
}
}
Tengo una clase LineReader
en mi proyecto MiscUtil . Es un poco más general que las soluciones dadas aquí, principalmente en términos de la forma en que puedes construirlo:
- De una función que devuelve una secuencia, en cuyo caso usará UTF-8
- Desde una función que devuelve una secuencia, y una codificación
- De una función que devuelve un lector de texto
- De solo un nombre de archivo, en cuyo caso usará UTF-8
- De 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
sí. Esta es la razón por la que se necesita Func<Stream>
y Func<TextReader>
lugar de la secuencia o el lector directamente; necesita poder diferir la apertura hasta que lo necesite. Es el propio iterador (que se elimina automáticamente mediante un ciclo foreach
) que cierra el recurso.
Como Marc señaló, esto funciona muy bien en LINQ. Un ejemplo que 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, abriéndolos y cerrándolos a medida que avanzan. Combinado con Push LINQ, puedes hacer todo tipo de cosas agradables :)
No es una clase particularmente "complicada", pero es realmente útil. Aquí está la fuente completa, por conveniencia, si no desea descargar MiscUtil. La licencia del código fuente está aquí .
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();
}
}
}
Un enfoque simplista (no eficiente de la memoria) de iterar cada línea en un archivo es
foreach (string line in File.ReadAllLines(file))
{
..
}
Un poco más elegante es el siguiente ...
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using (var streamReader = new StreamReader(fileStream))
{
while (!streamReader.EndOfStream)
{
yield return reader.ReadLine();
}
}
}