I have a LineReader
class in my MiscUtil project. It's slightly more general than the solutions given here, mostly in terms of the way you can construct it:
- From a function returning a stream, in which case it will use UTF-8
- From a function returning a stream, and an encoding
- From a function which returns a text reader
- From just a filename, in which case it will use UTF-8
- From a filename and an encoding
The class "owns" whatever resources it uses, and closes them appropriately. However, it does this without implementing IDisposable
itself. This is why it takes Func<Stream>
and Func<TextReader>
instead of the stream or the reader directly - it needs to be able to defer the opening until it needs it. It's the iterator itself (which is automatically disposed by a foreach
loop) which closes the resource.
As Marc pointed out, this works really well in LINQ. One example I like to give is:
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;
This will stream all the errors from a whole bunch of log files, opening and closing as it goes. Combined with Push LINQ, you can do all kinds of nice stuff :)
It's not a particularly "tricky" class, but it's really handy. Here's the full source, for convenience if you don't want to download MiscUtil. The licence for the source code is 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();
}
}
}