Components
File: Chained Streams and Stream Segments
- Uploaded:
- April.22.09
- Modified:
- April.22.09
- File Size:
- 10 KB
- Downloads:
- 786
- Version
- 1.0
Set of utility classes I needed for a personal project of mine. One allows you to
wrap a Stream so
only a limited area of it can be accessed and the other makes multiple Streams
together act like they were a single stream. All with nearly zero overhead!
Details
I've seen some crappy wrappers for this purpose already, but most of them weren't thought fully
through and didn't follow the System.IO.Stream interface. So here's a set of
wrappers with well-defined behavior that fully implement all System.IO.Stream
methods!
PartialStream
When working with Streams in .NET, sometimes it is necessary to provide a callee with only partial access to a Stream. Either for reasons of security or just as an exercise in defensive programming: if the user of your code should not seek to certain points in a stream, it's better to prevent them from doing so in the first place.
After I designed a package file format for my game programming needs, I wanted to expose the contents
of a single file to my callers, but not allow them to read beyond the end of the file (and thereby
into the next file stored in the package), so I needed to somehow limit them to the part of the
package Stream that covered the contents of a requested file.
To make this possible, I created the PartialStream class, a thin wrapper that behaves
like a Stream, but that's actually a view to a part of a larger Stream.
After deciding what to do when the outer Stream doesn't support seeking and how to
best behave on write requests that exceed the end of the Stream, I came up with an
implementation that almost perfectly mimics a Stream.
Limitations
You cannot extend the length of a PartialStream since it would possibly overwrite any
data that follows the window covered by the PartialStream in the outer Stream.
Theoretically, this limitation could be lifted if the PartialStream ended at the same
byte the outer Stream ends at, but for Streams that don't support seeking
it's impossible to determine the end, so extending a PartialStream's length has been
disabled for good.
Using the PartialStream class from multiple threads, even if each thread has its own
PartialStream, is dangerous if any of the PartialStreams share the same
outer Stream. This isn't a problem with the PartialStream class, it's a
design shortcoming of the Stream class. Since there aren't any ReadAt()
or WriteAt() methods, you have to position the file pointer first and then call
Read()/Write(). Without synchronization, the result is two concurrent
writes that will run in undefined order after any of the two offsets.
Example
ChainStream
This is the opposite of the PartialStream class. The ChainStream combines
multiple separate Streams into a single Stream, allowing you to access
them as if they were just one big continuous Stream.
If you're working with data that has been split into several files or if you're trying to add a
header to data that is already containing in a MemoryStream, you can use this class
to avoid time-consuming copies and fuse the Streams on the fly.
Depending in the capabilities of the chained Streams, the ChainStream will
support reading, writing and seeking. However, this is an all-or-none decision: If just one
Stream doesn't support reading, the ChainStream will block any read attempt,
not only if the offset being read falls into the affected Stream.
This decision had to be made to not violate the Stream interface. CanRead,
CanSeek and CanWrite are booleans, so you cannot return values like
"mostly" or "a bit". A Stream either supports reading everywhere or it doesn't.
Writing works like this: If one or more of the chained Streams don't support seeking,
the ChainStream acts like an unseekable Stream -- data is appended to
the end of it, meaning the last Stream in the chain. If all chained
Streams support seeking, writes will overwrite data contained in the chained
Streams and jump to the next Stream in the chain whenever the end of
the current Stream is reached. Writing to the end of the ChainStream
extends the length of the final Stream in the chain.
Limitations
Just like already explained in the PartialStream limitations, due to a shortcoming in the design
of the Stream class, you should not access a single Stream from multiple threads. That includes
the scenario where each thread has its own ChainStream, but one of more of the chained
streams are shared between the ChainStream instances.
Example
Features
-
Wrapper fully implements the entire
System.IO.Streaminterface - Unit tests with 100% test coverage included
- Efficiently implemented, almost zero overhead
The ChainedStream and PartialStream classes are part of
the Nuclex.Support library from the Nuclex Framework. You can find the most
recent release of the code on the framework's CodePlex site:
http://nuclexframework.codeplex.com/