Did you ever want to report progress when reading or writing streams in C#? You could of course do reporting manually, when issuing a read or write. This would of course be cumbersome and error prone. But what if the stream you want to monitor is read from or written to by someone else, such as a StreamWriter or CopyTo?
Full source code for this post can be found on GitHub.
We can solve this entirely by making a progress reporting stream following the classic decorator design pattern: you create a wrapper implementation of the abstract Stream-class, add read- and write-events. Then delegate the responsibility of the actual reading and writing to the wrapped implementation of Stream, such as FileStream or any other stream, while also firing the added events.
The use of such a ProgressReportingStream could look like this:
which will print the following in the console:
Status: 7 bytes written (7 total) Status: 19 bytes written (26 total) Status: 8 bytes written (34 total)
The implementation of the ProgressReportingStream class could look something like this. The most significant parts are the overrides of the methods
Write, defined on lines 21-31. This is where we invoke the events defined on lines 4 to 17 of type StreamUpdate-delegate. The constructor is parameterized by the wrapped stream, in the samples, a MemoryStream, but any stream will of course do.
It is however worth mentioning that the value used for reads is the actual number of bytes read, not the number of bytes requested. This is important, as you can request 4k bytes but only receive 1 byte.
No matter how you read or write the stream, you will get events fired. The only caveats are, readings are often buffered (by default the StreamReader class reads 1024 bytes and a minimum of 128 bytes). This is visible in the following sample, were only one read is reported.
Another sample, using the Stream.CopyTo method with reporting:
Here it is worth noting the extra empty read at the end which results in zero bytes being read. This signals that the copying is done, and the CopyTo method stops.
The decorator pattern provides an elegant and smooth method of wrapping existing classes.