Search

Advertising

Home Downloads Components

Components

Folder Path: \ Components \

File: Chained Streams and Stream Segments

file.png
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

using(MemoryStream memoryStream = new MemoryStream()) {
  memoryStream.SetLength(123);
  PartialStream partialStream = new PartialStream(memoryStream, 23, 100);

  Debug.Assert(partialStream.Position == 0);

  byte[] test = new byte[10];
  int bytesRead = partialStream.Read(test, 0, 10);

  Debug.Assert(bytesRead == 10);
  Debug.Assert(partialStream.Position == 10);

  bytesRead = partialStream.Read(test, 0, 10);

  Debug.Assert(bytesRead == 10);
  Debug.Assert(partialStream.Position == 20);
}

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

MemoryStream stream1 = new MemoryStream(new byte[10]);
MemoryStream stream2 = new MemoryStream(new byte[10]);

stream1.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
stream2.Write(new byte[] { 6, 7, 8, 9, 10 }, 0, 5);

ChainStream chainer = new ChainStream(stream1, stream2);

chainer.Position = 3;
byte[] buffer = new byte[15];
int bytesRead = chainer.Read(buffer, 0, 14);

Debug.Assert(bytesRead == 14);

byte[] expected = new byte[] {
  4, 5, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 0, 0, 0
};
for(int index = 0; index < expected.Length; ++index) {
  Debug.Assert(buffer[index] == expected[index]);
}

Features

  • Wrapper fully implements the entire System.IO.Stream interface
  • 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/



Joomla Template by Joomlashack