Leyendo un archivo línea por línea en C #

Estoy tratando de leer algunos archivos de texto, donde cada línea debe ser procesada. En este momento solo estoy usando StreamReader, y luego leo cada línea individualmente.

Me pregunto si existe una forma más eficiente (en términos de LoC y legibilidad) para hacer esto usando LINQ sin comprometer la eficiencia operativa. Los ejemplos que he visto implican cargar todo el archivo en la memoria y luego procesarlo. En este caso, sin embargo, no creo que sea muy eficiente. En el primer ejemplo, los archivos pueden obtener hasta aproximadamente 50k, y en el segundo ejemplo, no es necesario leer todas las líneas del archivo (los tamaños suelen ser <10k).

Se podría argumentar que hoy en día realmente no importa para estos archivos pequeños, sin embargo, creo que ese tipo de enfoque conduce a un código ineficiente.

Primer ejemplo:

// Open file using(var file = System.IO.File.OpenText(_LstFilename)) { // Read file while (!file.EndOfStream) { String line = file.ReadLine(); // Ignore empty lines if (line.Length > 0) { // Create addon T addon = new T(); addon.Load(line, _BaseDir); // Add to collection collection.Add(addon); } } } 

Segundo ejemplo:

 // Open file using (var file = System.IO.File.OpenText(datFile)) { // Compile regexs Regex nameRegex = new Regex("IDENTIFY (.*)"); while (!file.EndOfStream) { String line = file.ReadLine(); // Check name Match m = nameRegex.Match(line); if (m.Success) { _Name = m.Groups[1].Value; // Remove me when other values are read break; } } } 

Puede escribir un lector de línea basado en LINQ con bastante facilidad usando un bloque iterador:

 static IEnumerable ReadFrom(string file) { string line; using(var reader = File.OpenText(file)) { while((line = reader.ReadLine()) != null) { SomeType newRecord = /* parse line */ yield return newRecord; } } } 

o para hacer feliz a Jon:

 static IEnumerable ReadFrom(string file) { string line; using(var reader = File.OpenText(file)) { while((line = reader.ReadLine()) != null) { yield return line; } } } ... var typedSequence = from line in ReadFrom(path) let record = ParseLine(line) where record.Active // for example select record.Key; 

entonces tiene ReadFrom(...) como una secuencia perezosamente evaluada sin buffering, perfecta para Where etc.

Tenga en cuenta que si utiliza OrderBy o el GroupBy estándar, deberá almacenar en búfer los datos en la memoria; si necesita agrupar y agregar, “PushLINQ” tiene un código elegante que le permite realizar agregaciones en los datos, pero descartarlo (sin almacenamiento en búfer). La explicación de Jon está aquí .

Es más fácil leer una línea y comprobar si es nula o no, para comprobar EndOfStream todo el tiempo.

Sin embargo, también tengo una clase LineReader en MiscUtil que hace que todo esto sea mucho más simple: básicamente expone un archivo (o un Func como un IEnumerable que te permite hacer cosas LINQ sobre él. Así que puedes hacer cosas como:

 var query = from file in Directory.GetFiles("*.log") from line in new LineReader(file) where line.Length > 0 select new AddOn(line); // or whatever 

El corazón de LineReader es esta implementación de IEnumerable.GetEnumerator :

 public IEnumerator GetEnumerator() { using (TextReader reader = dataSource()) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } } 

Casi todo el rest de la fuente solo está dando formas flexibles de configurar dataSource (que es un Func ).

NOTA : debe tener cuidado con la IEnumerable , ya que dará como resultado que el archivo se abra durante el procesamiento.

Por ejemplo, con la respuesta de Marc Gravell:

 foreach(var record in ReadFrom("myfile.csv")) { DoLongProcessOn(record); } 

el archivo permanecerá abierto durante todo el proceso.

¡Gracias a todos por sus respuestas! Decidí ir con una mezcla, centrándome principalmente en la de Marc, ya que solo necesitaré leer líneas de un archivo. Supongo que se podría argumentar que la separación es necesaria en todas partes, pero ¡eh, la vida es muy corta!

En cuanto a mantener el archivo abierto, eso no será un problema en este caso, ya que el código es parte de una aplicación de escritorio.

Por último, noté que todos ustedes usaron una cadena en minúscula. Sé que en Java hay una diferencia entre cadenas en mayúsculas y no en mayúsculas, pero pensé que en C # la cadena en minúsculas era solo una referencia a String en mayúsculas.

 public void Load(AddonCollection collection) { // read from file var query = from line in LineReader(_LstFilename) where line.Length > 0 select CreateAddon(line); // add results to collection collection.AddRange(query); } protected T CreateAddon(String line) { // create addon T addon = new T(); addon.Load(line, _BaseDir); return addon; } protected static IEnumerable LineReader(String fileName) { String line; using (var file = System.IO.File.OpenText(fileName)) { // read each line, ensuring not null (EOF) while ((line = file.ReadLine()) != null) { // return trimmed line yield return line.Trim(); } } }