Introduction
Streams are a fundamental part of .NET, but lack the ability to notify the client of changes. What do I mean by notify? Notification is the ability for the client to be aware, at all time, of the state and transition of a stream. In other words, no change is made to the stream without the client being aware of that change, regardless of the number of references to that stream and where the change occurred.
This article describes various ways that a stream can be implemented to allow notification to occur.
Notifying Client of Stream Changes
NotifyStream
attempts to solve the notification problem. The client can subscribe to events so that it can be notified when changes have occurred. To maintain compatibility with the multitude of classes and components, NotifyStream
inherits directly from Stream
, allowing it to be used by classes such as StreamReader
.
public class NotifyStream : Stream
{
...
}
At a very fundamental level, all NotifyStream
does is wrap another stream (herein will be referred to as the base stream). So, the base stream needs to be passed to the constructor as shown in the following code:
public class NotifyStream : Stream
{
m_baseStream = baseStream;
public NotifyStream( Stream baseStream )
{
m_baseStream = baseStream;
}
...
}
This allows all calls to be forwarded to the base stream. Some operations are required as they are abstract in the Stream
class, others are optional, but providing them will allow for a more complete implementation. As you can see in the code below, the operations in NotifyStream
are nothing more then pass-through, operating directly on the base stream.
public class NotifyStream : Stream
{
...
public override bool CanWrite
{
get{return m_baseStream.CanWrite;}
}
public override IAsyncResult BeginRead(
byte[] buffer,
int offset,
int count,
AsyncCallback callback,
object state)
{
return m_baseStream.BeginRead(buffer,
offset,
count,
callback,
state);
}
public override int EndRead(
IAsyncResult asyncResult)
{
return m_baseStream.EndRead(asyncResult);
}
public override long Seek(long offset, SeekOrigin origin)
{
return m_baseStream.Seek(offset, origin);
}
public override long Length
{
get{return m_baseStream.Length;}
}
public override int Read(
byte[] buffer,
int offset,
int count)
{
return m_baseStream.Read(buffer, offset, count);
}
...
}
What has been discussed thus far seems quite uninteresting and appears to perform no special operations on the base stream. It is just an added level of indirection. What makes this stream powerful is the ability to send notifications when the stream has changed, regardless of what reference makes the change (as is shown below). As you can see, each write operation raises the StreamChanged
event. That way the client can always be notified.
public class NotifyStream : Stream
{
...
public override void Write(
byte[] buffer,
int offset,
int count)
{
m_baseStream.Write(buffer, offset, count);
OnStreamChanged( new EventArgs() );
}
public override void WriteByte(byte value)
{
m_baseStream.WriteByte(value);
OnStreamChanged( new EventArgs() );
}
public event EventHandler StreamChanged;
protected virtual void OnStreamChanged( EventArgs ea )
{
if( StreamChanged != null )
StreamChanged(this, ea);
}
}
Monitoring Stream Version
NotifyStream
provides an elegant way of informing the client when changes have occurred, but it comes at a cost. The cost being that there is added overhead when events are dispatched. As a lightweight alternative, you could implement VersionStream
which, rather than providing event support, implements a counter that is incremented whenever the stream is modified. By checking this counter, you can determine when it has changed, and even how many times it has changed.
As you can see below, VersionStream
is similar in design to NotifyStream
where the only implementation change is that a version is incremented rather then an event being raised.
public class VersionStream : Stream
{
private Stream m_baseStream = null;
private long m_curVersion = 0;
public VersionStream( Stream baseStream )
{
m_baseStream = baseStream;
}
...
public override void Write(
byte[] buffer,
int offset,
int count)
{
m_baseStream.Write(buffer, offset, count);
m_curVersion++;
}
public override void WriteByte(byte value)
{
m_baseStream.WriteByte(value);
m_curVersion++;
}
public long Version
{
get{return m_curVersion;}
}
}
Alternative Designs
Both NotifyStream
and VersionStream
are not meant to be a means to an end, but rather a starting point for more tailored implementations. This section discusses some alternative implementations.
A consequence of the above designs is that they inherit directly from Stream
, which means that you are limited in functionality and cannot easily expose specialized stream operations. If you plan to always wrap a specific type of stream, such as a MemoryStream
or FileStream
, it might benefit you to inherit from that stream directly, allowing you to forward any specialized operations.
You can add any additional operations that you need for your application.
You can add more events for your application to consume. For example, you could implement an event for when data is read from the stream. In addition, you could provide additional information to the event handler such as the content that was added. Right now only the basic EventHandler
is used.
Conclusion
Both the NotifyStream
and VersionStream
have the ability to provide notification to the client when changes have occurred. NotifyStream
employs a push mechanism in which when a change is made, events are raised regardless of the current execution path. VersionStream
employs a pull mechanism in which it is the responsibility of the client to query for the version and perhaps compare it with some previous version.
There are several benefits that these two classes have. They are useful in libraries where you may not have control over what the client does but need to monitor the state of the stream when changes occur. This might be used when the contents of a stream affect the state of the library. One such internal state may be the need to refresh a cache. There are many classes in .NET such as Image
and XmlDocument
which are loaded from a stream but hold no persistent references to the stream. In this case, you might want to monitor the stream for changes so that you can invalidate your cache.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.