I've spent quite a bit of time getting familiar with the .NET Stream classes. Usually I learn a lot by studying the class design of professional, commercial-grade frameworks, but I have to say that something doesn't quite smell right here.
System.IO.Stream is an abstract class representing a sequence of bytes. It has 10 abstract method/properties: Read, Write, Flush, Length, SetLength, Seek, Position, CanRead, CanWrite, CanSeek
. So many abstract members makes it cumbersome to derive from, because you have to override all those methods, even if most end up just throwing NotImplemented
.
Users of Stream classes are expected to call CanRead
, CanWrite
, or CanSeek
to find out the capabilities of the Stream, or I suppose just go ahead and call Read
, Write
, or Seek
and see if it throws NotImplemented
. Is it just me, or is this crummy design?
Though there are many nits I'd like to pick with the Stream
class design, the main one I'd like to ask about is this: Why didn't they use interfaces, like IReadable
, IWriteable
, ISeekable
, instead? Then a new Stream class could gracefully derive from the interfaces it supports. Isn't this the object-oriented way of doing things? Or am I missing something?
Update: It was pointed out that the value CanRead
et al can change at runtime—for example if a FileStream
is closed—and the point is taken. However, I remain unconvinced that this is a good design. From where I'm from, trying to read from a file that's already been closed is a bug, or at least an exceptional condition. (And thus throwing an exception is a natural way to handle this situation.)
Does this mean that every time I'm about to Read
from a Stream
, I should check CanRead
? And would that mean I should put a lock in place to avoid a race condition, if it's possible for the value to change sometime in between the CanRead
call and the Read
call?
Update Aug 7 2010: The consensus here seems to be the Stream design is pretty good as it stands. But let me ask one more time just to be 100% sure: People are writing something like this every time they read from a Stream?
// s is a Stream
lock(s)
{
if (s.CanRead)
{
s.Read(buf, 0, buf.Length);
}
}
To do it right, you just need to know that Dispose() calls Close() (a pretty intimate piece of implementation trivia). Further, in the Dispose(bool) cases, you need to ignore Dispose() and just write a Dispose(bool) implementation that makes sure to chain the base class method.
Streams involve three fundamental operations: Reading - transferring data from a stream into a data structure, such as an array of bytes. Writing - transferring data to a stream from a data source. Seeking - querying and modifying the current position within a stream.
If your stream is using an operating system handle to communicate with its source, consider using a subclass of SafeHandle for this purpose. This method is called by the public Dispose method and the Finalize method. Dispose invokes the protected Dispose method with the disposing parameter set to true .
The stream is basically the sequence of bytes passing through the communication path. There are two main streams: the input stream and the output stream. The input stream is used for reading data from file (read operation) and the output stream is used for writing into the file (write operation).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With