Ich habe eine LineReader
-Klasse in meinem MiscUtil-Projekt. Sie ist etwas allgemeiner als die hier angegebenen Lösungen, hauptsächlich in Bezug darauf, wie Sie sie konstruieren können:
- Aus einer Funktion, die einen Stream zurückgibt, in diesem Fall wird UTF-8 verwendet
- Aus einer Funktion, die einen Stream zurückgibt, und eine Codierung
- Aus einer Funktion, die einen Textleser zurückgibt
- Nur aus einem Dateinamen, in diesem Fall wird UTF-8 verwendet
- Aus einem Dateinamen und einer Codierung
Die Klasse "besitzt" die von ihr verwendeten Ressourcen und schließt sie angemessen. Sie implementiert jedoch nicht selbst IDisposable
. Das ist der Grund, warum sie Func
und Func
anstelle des Streams oder des Readers direkt nimmt - sie muss die Öffnung bis zu dem Zeitpunkt verschieben können, an dem sie benötigt wird. Der Iterator selbst (der automatisch durch eine foreach
-Schleife entsorgt wird) schließt die Ressource.
Wie Marc angemerkt hat, funktioniert dies sehr gut in LINQ. Ein Beispiel, das ich gerne gebe, ist:
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;
Dies streamt alle Fehler aus einer Vielzahl von Protokolldateien, öffnet und schließt dabei. In Kombination mit Push LINQ können Sie allerlei schöne Dinge tun :)
Es handelt sich nicht um eine besonders "tricky" Klasse, aber sie ist wirklich praktisch. Hier ist der vollständige Quellcode, falls Sie MiscUtil nicht herunterladen möchten. Die Lizenz für den Quellcode finden Sie hier.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace MiscUtil.IO
{
///
/// Liest eine Datenquelle zeilenweise. Die Quelle kann eine Datei, ein Stream oder ein Textleser sein. In jedem Fall wird die Quelle nur geöffnet, wenn der Enumerator abgerufen wird, und wird geschlossen, wenn der Iterator entsorgt wird.
///
public sealed class LineReader : IEnumerable
{
///
/// Mittel zur Erstellung eines Textreaders zum Lesen.
///
readonly Func dataSource;
///
/// Erstellt einen LineReader aus einer Streamquelle. Der Delegat wird erst aufgerufen, wenn der Enumerator abgerufen wird. UTF-8 wird verwendet, um den Stream in Text zu decodieren.
///
/// Datenquelle
public LineReader(Func streamSource)
: this(streamSource, Encoding.UTF8)
{
}
/// < summary>
/// Erstellt einen LineReader aus einer Streamquelle. Der Delegat wird erst aufgerufen, wenn der Enumerator abgerufen wird.
///
/// Datenquelle
/// < param name = "encoding">Codierung, die zum Decodieren des Streams in Text verwendet wird
public LineReader(Func streamSource, Encoding encoding)
: this(() => new StreamReader(streamSource(), encoding))
{
}
///
/// Erstellt einen LineReader aus einem Dateinamen. Die Datei wird erst geöffnet (oder sogar auf Existenz überprüft), wenn der Enumerator abgerufen wird. UTF8 wird verwendet, um die Datei in Text zu decodieren.
///
/// Datei zum Lesen
public LineReader(string filename)
: this(filename, Encoding.UTF8)
{
}
/// < summary>
/// Erstellt einen LineReader aus einem Dateinamen. Die Datei wird erst geöffnet (oder sogar auf Existenz überprüft), wenn der Enumerator abgerufen wird.
///
/// Datei zum Lesen
/// Zum Decodieren der Datei in Text zu verwendende Codierung
public LineReader(string filename, Encoding encoding)
: this(() => new StreamReader(filename, encoding))
{
}
///
/// Erstellt einen LineReader aus einer Textleserquelle. Der Delegat wird erst aufgerufen, wenn der Enumerator abgerufen wird.
///
/// Datenquelle
public LineReader(Func dataSource)
{
this.dataSource = dataSource;
}
///
/// Enumeriert die Datenquelle zeilenweise.
///
public IEnumerator GetEnumerator()
{
using (TextReader reader = dataSource())
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
///
/// Enumeriert die Datenquelle zeilenweise.
///
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}