Introduction
I've long wished it was easier to monitor variables, i.e., to observe changes in their values without having to disturb normal processing flow - sort of like a database trigger (although that's only occurred to me since writing PropertyWatch
, and I've never actually used a database trigger).
The introduction of the “Property” into contemporary languages offers an opportunity to fulfill my desires; when I first encountered C# (i.e.., when I was calling it C ‘crunch'), I was disappointed on finding neither the language nor the environment appeared to provide out-of-the-box property events. I can only envisage the need for two such events - ReadProperty
and ChangePropertyValue
; it is the latter that PropertyWatch
implements, I'll attend to the the former if and when needed.
Using the code
The PropertyWatch
class provides three methods, each having instance and static variants, a delegate definition, and an EventArgs
derivative; there are no public properties or fields. I've taken out the Singleton implementation, argument checks, and exception handling; their inclusion would necessitate shipping other parts of my Toolkit, which would obfuscate the issue addressed by this article.
At the heart of PropertyWatch
lies a hierarchical collection object of the form:
Dictionary<object, Dictionary<string, List<Observer>>>
Working left-to-right, object
is an instance object whose properties are under observation, string
is the name of the property being observed, and Observer
is the delegate method that's invoked when the value of the property changes. The collection implementing class, observers
, is private
and nested within PropertyWatch
. The public Observer
delegate and its companion EventData
class are also nested within PropertyWatch
. The three methods provided by PropertyWatch
are, Attach
, Detach
, and Notify
, whose definitions are thus:
public bool Attach(string _propertyName, Observer _observer);
public static bool Attach(object _instanceObject, string _propertyName, Observer _observer);
public bool Detach(string _propertyName, Observer _observer);
public static bool Detach(object _instanceObject, string _propertyName, Observer _observer);
public void Notify(object _newValue);
public static void Notify(object _instanceObject, object _newValue);
private static void notify(MethodBase _caller, object _instanceObject, object _newValue);
The Attach
and Detach
methods are similar in purpose to the add and remove accessors of an event (often accessed via the “+=
” and “-=
” operators). The arguments are hopefully self explanatory; these methods do what they say, attach and detach an observer to monitor changes to the value of a property of an instance of the property's declaring class.
The Notify method must be invoked by a set
accessor, a primary objective was to minimise the arguments required. If the property declaring type inherits from PropertyWatch
, then the instance variant of Notify can be invoked, which only requires the value as an argument
public string Property
{
get{return this.backingVariable;}
set
{
PropertyWatch.Notify(this, value);
this.Notify(value);
this.backingVariable = value;
}
}
Finally, there's a sealed
class Tools
that contains a couple of support methods which I discuss in the following Points of interest section; these are in a separate class because the methods have wider applicability than in the PropertyWatch
alone.
Points of interest
- Why not use normal event handling? That's what I assumed I would do, but I couldn't figure out a way of doing so which didn't involve constructing and emitting events at runtime - which felt like and looked like a sledgehammer. So, I opted for using the hierarchical collection class, as it best represents my view of the problem. If anyone can suggest a simple event based implementation, please do so - I get as much enjoyment from trashing code as I do from scratching it out in the first place. As you can see in the code, there's not much to it.
- Why not set the property value in
Notify
? The SetValue
method provided by PropertyInfo
invokes the set
accessor, hence we end up in a recursive situation. If we detect the recursive situation by interrogating the stack in the set
accessor to avoid calling Notify
again, that doesn't solve the problem - a statement of the form this.backingVariable = value;
, has to be written somewhere - what better place than in the set
accessor itself.. I don't think setting the value in Notify
can be done, no matter how hard we try - the property itself has no cognizance of backing variables, and nor should it - there may not even be such, the value might be packaged up in a message and set into the ether to who knows where. So, the principle is that the set
accessor is not relieved of any if its “normal” duties.
- Why must the
set
accessor pass its class instance to the static implementation of Notify
, can't Notify
work it out? Well, not as easily as I assumed it would be, the design only had the value being passed. But, I can't see an easy way of getting the caller's “this
” out of MethodBase
or PropertyInfo
without going down the RelectedType
track, which I know from experience is hard going. It was this shortcoming that led to the provision of instance implementations of the three methods - in practice, I've found that I use the static method variants as I usually have another class that I want to use as the base for my data classes - if C# had multiple inheritance, then..., but that's another story. - You'll observe that the scheme relies on property names, the values of which have to come from somewhere. The
Notify
method derives the property name from the set
accessor method name, using it to get the PropertyInfo
and to access PropertyWatch
's Observers
collection. However, in the Attach
/Detach
methods, I can see no convenient, or for that matter inconvenient, way of getting the property name from a reliable source. Hence, I've adopted a clumsy scheme involving a struct of static strings as is implemented in the PropertyWatchData
class; originally, this had the property names as constants, but red is to me as it is to a bull. Which is where the support method PropertyWatch.GetPropertyName
comes into play. The default constructor must initialise all the properties; note, this means that if PropertyWatch
is used on structs, the constructor must initialise all the fields and all the properties - don't ask me why the former is necessary, I don't know either. Each property set
accessor must interrogate the IsConstructing
property; if it's true
, then the Tools.GetPropertyName
method is invoked, and its result is stored in the appropriate static variable in the <dataClass>.PropertyNames
struct. I changed a property name from Balance
to AccountBalance
, and I changed the corresponding backing variable name similarly, but I willfully neglected to change the corresponding name in PropertyNames
, but the scheme still works, and you'll see that the ObservationsLog
reports the property name correctly as AccountBalance
.
You'll note that the IsConstructing
property doesn't have a backing variable, rather it returns the result from the static method Tools.DefaultConstructing
, which interrogates the stack to determine if it contains the default constructor; if so, then true
is returned; otherwise, false
is returned (as well as red code, I hate flag variables - perhaps even more so).
Again - suggestions for improvement will be gratefully and gracefully received.
- My code has no attribution, use it in any way you see fit, including passing it off as your own if you're so inclined, it's thee who must live with thine deceit, not I.
I've used terms such as Observer that I know others use in special contexts such as Patterns and Factories, I make no claim to have implemented such here. I've been using terms like Observer for more years than many of those, who insist that such words be reserved for their specific contextual meaning, have walked this earth.
I make no apologies for offending such people; programmers don't invent most of the words they use, they purloin them from the English language at large. A major reason English is the most widely spoken language is that there is no language police, unlike other languages such as French, German, Mandarin etc. - let's not allow programmers (even if their name is Bill Gates) become the English Language Police (ELP).
- Why do I write long lines of code? Because I have a milspec 23” hi-res CRT that I run 1920x1440 and sometimes higher. It only cost me $AUS65.00 ($US50, ₤UK25, €EU35) - I've not seen an LCD or Plasma to touch its clarity and vibrant, yet soft, colours - sure, it uses a lot of real estate and weighs a massive 43 KG, but it's black, and it's beautiful. Of course, its low price (at a government auction) is entirely due to the advent of flat screens.
History
- 13/01/07
- Late inclusions in version 1.0.
PropertyWatch
implements IsConstructing
as a protected property, obviating the necessity of implementing IsConstructing
in classes that have PropertyWatch
as their base.PropertyWatch
defines a corresponding interface for data classes that do not inherit from PropertyWatch
.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.