Making Streams Enumerable
In this post, I’ll show you a quick hack to make Stream objects enumerable. This is nice because once done, you can treats Streams as C# 2.0 iterators, which lets us play all kinds of fun games with lambda expressions.
First we’ll start with an adapter class to expose IEnumerable<T>:
public class StreamAsEnumerable : IEnumerable<char>
{
StreamReader _reader;
public StreamAsEnumerable(Stream stm)
{
if (stm == null)
throw new ArgumentNullException("stm");
if (!stm.CanSeek)
throw new ArgumentException("stream must be seekable", "stm");
if (!stm.CanRead)
throw new ArgumentException("stream must be readable", "stm");
_reader = new StreamReader(stm);
}
public IEnumerator<char> GetEnumerator()
{
int c = 0;
while ((c = _reader.Read()) >= 0)
{
yield return (char)c;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
This is a very straightforward class – inherit from IEnumerable (in this case we’ll take the char flavor to process text), wrap the Stream in a Stream reader and then implement IEnumerator<T>.GetEnumerator and IEnumerable.GetEnumerator().
The GetEnumerator code is straight forward – read a char from the stream reader and if it’s non-negative, yield return it.
In practice, you can start to write code like this:
StreamAsEnumerable chars = new StreamAsEnumerable(stm);
int lines = chars.Count(c => c == '\n');
which is a two liner to count all the lines in a stream (note that in production, you should use Environment.NewLine for newline checking, but this passes).
Now imagine instead that you write a version of StreamAsEnumerable that is IEnumerable<string> or IEnumerable<DSLToken> where DSLToken is a class representing a token for a domain specific language. This would let you write a parser like this:
StreamAsTokenEnumerator tokens = new StreamAsTokenEnumerator(Stream);
foreach (DSLToken token in tokens)
{
FeedMyDFA(token);
}
all of a sudden, compilers and interpreters feel so much easier.