WrappingStream Implementation
In a previous post , I
mentioned that a certain problem could be solved by creating “an
implementation of Stream that wraps another stream”. “Anonymous” asked
recently, “Can you send me the code of your straightforward solution that is
wrapper the class MemoryStream?”. Yes, but first I want to give another
example of where such a class is useful.
The first time I used BinaryReader , I wrote code similar to the
following:
public void DoSomething ( Stream stream )
{
// read a simple byte
int value = stream . ReadByte ();
// simplify more complex reading by using a BinaryReader
using ( BinaryReader reader = new BinaryReader ( stream ))
{
value = reader . ReadInt32 ();
value = reader . ReadInt32 ();
}
// back to simple reading
value = stream . ReadByte ();
}
Experienced users of BinaryReader will see the problem here: the BinaryReader
class takes ownership of the Stream with which it’s constructed, and disposes
it in Dispose. (This somewhat-important detail is only briefly mentioned in
the documentation for the Close method.) Thus the last call to
ReadByte fails; we have also closed the Stream even though our caller probably
expects it to still be open.
While the easy answer is to simply not Close/Dispose the BinaryReader (it
holds no unmanaged resources, and so nothing “bad” happens if it’s not
disposed), I feel guilty every time I don’t dispose an IDisposable object.
Moreover, tools like the .NET Memory Profiler have
a profiling mode that lists all non-disposed IDisposable objects (to help find
resource leaks); leaving this BinaryReader undisposed generates a false
positive and makes it harder to find the real leaks.
The WrappingStream class can be of use in this scenario by providing an
implementation of Stream that the BinaryReader can own and Dispose without
affecting the real stream:
using ( WrappingStream wrapper = new WrappingStream ( stream ))
using ( BinaryReader reader = new BinaryReader ( wrapper ))
{
value = reader . ReadInt32 ();
value = reader . ReadInt32 ();
}
// 'stream' is still valid here
Here, at long last, is the code for WrappingStream:
/// <summary>
/// A <see cref="Stream"/> that wraps another stream. The major feature of <see cref="WrappingStream"/> is that it does not dispose the
/// underlying stream when it is disposed; this is useful when using classes such as <see cref="BinaryReader"/> and
/// <see cref="System.Security.Cryptography.CryptoStream"/> that take ownership of the stream passed to their constructors.
/// </summary>
public class WrappingStream : Stream
{
/// <summary>
/// Initializes a new instance of the <see cref="WrappingStream"/> class.
/// </summary>
/// <param name="streamBase">The wrapped stream.</param>
public WrappingStream ( Stream streamBase )
{
// check parameters
if ( streamBase == null )
throw new ArgumentNullException ( "streamBase" );
m_streamBase = streamBase ;
}
/// <summary>
/// Gets a value indicating whether the current stream supports reading.
/// </summary>
/// <returns><c>true</c> if the stream supports reading; otherwise, <c>false</c>.</returns>
public override bool CanRead
{
get { return m_streamBase == null ? false : m_streamBase . CanRead ; }
}
/// <summary>
/// Gets a value indicating whether the current stream supports seeking.
/// </summary>
/// <returns><c>true</c> if the stream supports seeking; otherwise, <c>false</c>.</returns>
public override bool CanSeek
{
get { return m_streamBase == null ? false : m_streamBase . CanSeek ; }
}
/// <summary>
/// Gets a value indicating whether the current stream supports writing.
/// </summary>
/// <returns><c>true</c> if the stream supports writing; otherwise, <c>false</c>.</returns>
public override bool CanWrite
{
get { return m_streamBase == null ? false : m_streamBase . CanWrite ; }
}
/// <summary>
/// Gets the length in bytes of the stream.
/// </summary>
public override long Length
{
get { ThrowIfDisposed (); return m_streamBase . Length ; }
}
/// <summary>
/// Gets or sets the position within the current stream.
/// </summary>
public override long Position
{
get { ThrowIfDisposed (); return m_streamBase . Position ; }
set { ThrowIfDisposed (); m_streamBase . Position = value ; }
}
/// <summary>
/// Begins an asynchronous read operation.
/// </summary>
public override IAsyncResult BeginRead ( byte [] buffer , int offset , int count , AsyncCallback callback , object state )
{
ThrowIfDisposed ();
return m_streamBase . BeginRead ( buffer , offset , count , callback , state );
}
/// <summary>
/// Begins an asynchronous write operation.
/// </summary>
public override IAsyncResult BeginWrite ( byte [] buffer , int offset , int count , AsyncCallback callback , object state )
{
ThrowIfDisposed ();
return m_streamBase . BeginWrite ( buffer , offset , count , callback , state );
}
/// <summary>
/// Waits for the pending asynchronous read to complete.
/// </summary>
public override int EndRead ( IAsyncResult asyncResult )
{
ThrowIfDisposed ();
return m_streamBase . EndRead ( asyncResult );
}
/// <summary>
/// Ends an asynchronous write operation.
/// </summary>
public override void EndWrite ( IAsyncResult asyncResult )
{
ThrowIfDisposed ();
m_streamBase . EndWrite ( asyncResult );
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
public override void Flush ()
{
ThrowIfDisposed ();
m_streamBase . Flush ();
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position
/// within the stream by the number of bytes read.
/// </summary>
public override int Read ( byte [] buffer , int offset , int count )
{
ThrowIfDisposed ();
return m_streamBase . Read ( buffer , offset , count );
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
/// </summary>
public override int ReadByte ()
{
ThrowIfDisposed ();
return m_streamBase . ReadByte ();
}
/// <summary>
/// Sets the position within the current stream.
/// </summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
/// <returns>The new position within the current stream.</returns>
public override long Seek ( long offset , SeekOrigin origin )
{
ThrowIfDisposed ();
return m_streamBase . Seek ( offset , origin );
}
/// <summary>
/// Sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength ( long value )
{
ThrowIfDisposed ();
m_streamBase . SetLength ( value );
}
/// <summary>
/// Writes a sequence of bytes to the current stream and advances the current position
/// within this stream by the number of bytes written.
/// </summary>
public override void Write ( byte [] buffer , int offset , int count )
{
ThrowIfDisposed ();
m_streamBase . Write ( buffer , offset , count );
}
/// <summary>
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
/// </summary>
public override void WriteByte ( byte value )
{
ThrowIfDisposed ();
m_streamBase . WriteByte ( value );
}
/// <summary>
/// Gets the wrapped stream.
/// </summary>
/// <value>The wrapped stream.</value>
protected Stream WrappedStream
{
get { return m_streamBase ; }
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="WrappingStream"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose ( bool disposing )
{
// doesn't close the base stream, but just prevents access to it through this WrappingStream
if ( disposing )
m_streamBase = null ;
base . Dispose ( disposing );
}
private void ThrowIfDisposed ()
{
// throws an ObjectDisposedException if this object has been disposed
if ( m_streamBase == null )
throw new ObjectDisposedException ( GetType (). Name );
}
Stream m_streamBase ;
}
You’ll note that this class isn’t sealed; that’s because it can be a useful
base class for more specialised wrappers, some of which I hope to cover in
future posts.
Posted by
Bradley Grainger
on
May 06, 2009