Introduction
Consider the case when there's a need to monitor the change of a class attribute. Each time the change occurs, an event should be raised to notify all interested objects. For this, I have developed a simple class which I often use.
Here's an example of such a need: we have a class, working with a database, which often executes queries. And we need monitoring the database channel state - whether it's OK or not.
Background
To achieve this, each time we have a successful query, we see if the connection is OK, and when the query fails because of the database connection - the channel state becomes disconnected.
We then have something like this:
public class DBGateway
{
private bool connectionIsOk = false;
public bool ConnectionIsOK
{
get { return connectionIsOk; }
private set
{
connectionIsOk = value;
if (connectionIsOk == false && value == true)
RaiseConnectionRestored();
if (connectionIsOk == true && value == false)
RaiseConnectionLost();
}
}
private string connectionString;
public void ExecuteQuery()
{
try
{
ConnectionIsOK = true;
}
catch(SqlException)
{
ConnectionIsOK = false;
throw;
}
}
private void RaiseConnectionRestored()
{
if (ConnectionRestored != null) ConnectionRestored(this, EventArgs.Empty);
}
private void RaiseConnectionLost()
{
if (ConnectionLost!= null) ConnectionLost(this, EventArgs.Empty);
}
public event EventHandler ConnectionRestored;
public event EventHandler ConnectionLost;
}
The wrong thing about this code is that the database gateway class has an additional responsibility. We've got two events, functions for raising these events, and the logic for checking whether the value really changed.
The ValueMonitor<T> Class
ValueMonitor
is a simple class which encapsulates the logic of change checks. It has a ValueChanged
event, a Value
property, and a constructor which allows you to set the initial value of the monitored variable.
We need to pay special attention to value and reference types. The Equals
function returns what is really expected for the value types.
However, the result of equality comparison for reference types is the result of "pointers" comparison. Not what we actually need. That's why for reference types IEqualityComparer
is expected in the overloaded constructor version.
Consider the value change check code:
public class ValueMonitor<ValueType>:IValueMonitor<ValueType>
{
private ValueType aValue = default(ValueType);
private IEqualityComparer<ValueType> comparer = null;
public ValueType Value
{
get { return aValue; }
set
{
bool areEqual = false;
if (comparer == null)
areEqual = (aValue.Equals(value));
else areEqual = comparer.Equals(aValue, value);
if (areEqual == true) return;
ValueType oldValue = aValue;
aValue = value;
if (ValueChanged != null)
ValueChanged(oldValue, aValue);
}
}
}
Using the Code
Now the code for our DBGateway
class will look like this:
public class DBGateway
{
private ValueMonitor<bool> connectionIsOk = new ValueMonitor<bool>(false);
public IValueMonitor<bool> ConnectionIsOK
{
get { return connectionIsOk; }
}
private string connectionString;
public void ExecuteQuery()
{
try
{
connectionIsOk.Value = true;
}
catch(SqlException)
{
connectionIsOk.Value = false;
throw;
}
}
}
As you see, we got rid of additional logic, and our class became lighter, and we now can concentrate on the DB code rather than the value change monitoring.
Example Application
Of course, in the example code, we don't monitor the database connection. To see the working of the class, the user sets values true
and false
to the status
variable inside the Form
class.
The form handles the ValueChanged
event and shows the time of actual change of the variable.
Clicking the same button several times gives no effect. Changing the button does.
Points of interest
Usage examples:
- Monitoring state of any kind of channel like in the
DBGateway
example. - Monitoring the change of state of an object. Imagine we have an implementation of the State pattern and we want to be aware of the current state and its change. Then we have an enumeration like this:
public enum ObjectState
{
Disconnected,
LoggingIn,
Normal
}
Then, to provide value change monitoring, we add something like:
ValueMonitor<ObjectState> currentState =
new ValueMonitor<ObjectState>(ObjectState.Disconnected);
- The case when you receive the traffic from another client application and you'd like to monitor the IP address of the currently connected one. The situation is not so rare: two hosts may use different channels, and when one fails, the other (backup host) connects and the system is still stable.
History