Using interfaces would mean that the value of "CanRead" couldn't be changed at runtime. The "FileStream" class changes the "CanRead" property based on the current state of the file.
I think the classes are designed nicely. I would much rather check a property then attempt to do something and have to catch an exception. Interfaces fall short in the case of stream types that are of multiple "types". What type would be returned from a method that gets you a readable and writable stream? I agree the design isn't a true Object Oriented Design, but do you really want to treat streams in that manner? Some of the properties can change if the stream is closed or something else changes, what would happen in that case?
I think this question brings up a really interesting experiment though, why not try to design your own stream related classes. Publish your redesign on CodePlex or Google Code, it would be a great learning experience and would result in a potentially useful library for others to use.
They probably didn't use interfaces because, at the time, there were no extension methods. If you wanted every stream to have things like a default ReadByte method, you needed to use a class.
I wrote a blog post a couple months back about why I don't like IO.Stream and what I thought should be done. Essentially it boils down to streams not being very type-safe.
Interfaces can be overused, and this would be one of those cases. I think the current design is great. The fact that streams can change capabilities at runtime means that IReadable/IWritable/ISeekable would not obviate the need for CanRead, CanWrite and CanSeek, so you would just be increasing the complexity for no real gain other than eliminating a handful of stub methods and properties in your derived classes.
Personally I prefer that a stream class be easier to use than easier to write, because you'll be writing it once and using it many times.
You could take the (Java*) approach of writing a mostly-empty MyStream
class that inherits the base Stream
class, but provides most of the member methods (e.g., CanSeek()
) and embuing them with reasonable default behavior (e.g., throwing NotImplemented
). Then your real class simply extends your MyStream
class, implementing the two or three remaining methods that you really need.
As you reuse your MyStream
class, you'll save a lot of reinventing of the wheel.
* This is called an abstract adapter class in the Java libraries.
My issue with the stream class is the Length property - it doesn't specify how to implement a stream of unknown, unspecified, or infinite length!