However, I just can’t believe that my particular circumstances are so unique that they require a custom solution. Surely many other projects must face very similar problems. Hence, in the hope that it’s just my own ignorance and that someone will point out a better way, the following is an outline of what I see as the main design problems, why the typical approaches aren’t appropriate and what I came up with to solve them.
In almost all APIs, mine included, complex calculations and work-flows are achieved by routing information back and forth between a series of components that each do a particular task. This internal plumbing is either provided by higher-level functions within the API, left entirely up to the host application to handle, or some hybrid of the two where both high and low level functions are accessible. However, when some of the components support end-user interaction, neither the API nor the host application really has any idea what is going to be changed, when and in what order - so there needs to be some sort of automatic plumbing and flow management built-in.
This is where event notification helps. The idea is that events are generated whenever any of a component’s properties are changed. Other components can then register themselves as a listener for those events, which is usually done automatically when interconnections are made.
To better illustrate what I am on about, consider a
SunPathDiagram display component. It relies on a
GeoLocation object to know where it is on the Earth’s surface as well as a
TimeAndDay object to calculate the current Sun position. When either of these change, the sun-path diagram needs to regenerate itself and update the display. Rather than duplicate code with its own
setTimeAndDay() methods, it could just register itself as a listener for changes in both a
TimeAndDay object, and then update itself appropriately on each notification. This is just basic object-oriented programming, but the benefit is that the
TimeAndDay objects could be shared with
ShadowProjection objects, for example, to calculate overshadowing and generate visual shadows in the model. Thus, assigning values to the one shared object automatically updates many other aspects of an application without anyone having to worry about the organisation of internal plumbing.
The Design Issues as I See Them
There are lots of strategies for property change notification. In fact, the standard .NET
System.ComponentModel provides an
INotifyPropertyChanged interface that is usually used for this purpose. However, there are a couple of reasons why standard approaches are not that appropriate for the kinds of analysis classes I am currently working on. To explain why, I first need to outline the fundamental design issues involved as I see them.
Flexibility vs Performance
When implementing event notifications, you want to support them widely enough to allow for API uses that you may never even have dreamt of, whilst at the same time avoiding any performance loss (however small) by generating lots of events that are never actually listened to. Maximum flexibility means adding notifications to every property in every class. Avoiding wasted cycles on ignored events or even just checking for listeners during each property change means being a lot more judicious about exactly which classes and properties should generate them. The middle ground between these two isn’t always obvious.
Too Much Information
The next design issue is deciding what happens when a host application makes multiple sequential changes to several different properties in a class at once. Do you generate an event for each change or somehow accumulate them together? How do you know when the sequence of changes has ended? Are two or more accumulated changes to the same property all recorded or do you amalgamate them and keep just the first old and last new values?
All the approaches I have came across simply send notifications for each and every change. In fairness, most implementations in situations similar to mine seem to side-step the issue entirely by making the objects immutable - meaning that any change, be it one property or many, occurs as a single assignment which generates just one notification. Immutable objects have their place, but in my opinion that place is not in managed code that is trying to deal with dynamic visualisation. Every assignment leaves residue that has to be cleaned up later by the garbage collector, and is just a noticeable display glitch waiting to happen. This is especially true if the user is interactively changing one value in a complex object and generating 60 redraws a second as they drag a slider. Thus, I like my objects to be reusable and updatable.
The problem with generating events for each property change is that some listener responses are computationally expensive. If we take the
SunPathDiagram example used earlier, regenerating vertex buffers is costly so we don’t want to do it once for the latitude change, again for the longitude change, and then again for the time-zone. However, we don’t just want a single notification either because we use several different vertex buffers for different parts of the diagram. If the latitude, longitude or time-zone changes, we only update the sun-path lines. If the north offset angle changes, we shouldn’t need to update anything other than the rotation matrix. Thus we do need to know about each and every change, but in a manageable way.
Not Enough Information
My main problem with the
INotifyPropertyChanged interface is that it only takes a string containing the name of the property that has changed. Now for a start I have issues with relying on hard-coded magic strings that you just know will will be missed entirely by refactoring and very soon get out of sync with their related properties, let alone relying on string matching at the listener end to discriminate between properties. However, such notifications only tell a listener what changed, not by how much or what the original and new values are. For this you need to implement your own
event properties with your own
EventArgs sub-class to convey that information.
Recording before and after values to send with a change event generates a surprising amount of boilerplate code littered across every notifying property setter. This is because you have to copy the existing value before the change but generate the event notification after the change. As noted previously, you don’t want to be copying or sending anything unless you know there is at least one listener, so checks need to be made for this and whether notifications have been temporarily suspended, etc. All this needs extra lines of code and takes up precious processor cycles so I can certainly see why it is usually not done.
My way around some of these issues was to use my own generic
PropertyChangedEventArgs<T> class that contains a list of properties that have changed rather than just the data for a single change. If an API call affects just one property value, the change list contains just a single item. Where an API call affects more than one property values, these are automatically grouped into a single change list and sent at the end of all changes. Obviously multiple API calls to individual property setters will generate multitple events each with a single item list. However, each class with notifying properties also provides
resumeChangeEvents() methods. The
accumulatePropertyChangedEvents() method accumulates changes into a single change list that is sent when resumed, which allows users of the API to manually group their own changes.
For example:// Option One: Setting properties directly. aClassInstance.accumulatePropertyChangedEvents(); aClassInstance.PropertyOne = 1.5; aClassInstance.PropertyTwo = 6.9; aClassInstance.PropertyThree = 11.987; aClassInstance.PropertyFour = 3.14; aClassInstance.resumePropertyChangedEvents(); aClassInstance.recalculate(); // Option Two: Using setters and method chaining. aClassInstance.accumulatePropertyChangedEvents() .setPropertyOne(1.5) .setPropertyTwo(6.9) .setPropertyThree(11.987) .setPropertyFour(3.14) .resumePropertyChangedEvents() .recalculate();
Identifying the property that changed is done using a
PropertyId enum specific to each notifying class rather than as a string containing the property name. Yes the enum does need to be updated manually when properties change, but at least changes to the enum can be done using refactoring and it means that listener classes aren’t left waiting for misspelt or long-since renamed properties.
Being able to store before and after values for properties with different data types in a single list proved a bit awkward in C#. Solving it required another generic
PropertyChangedEventData<T> class to store both values as type
object, record their real data type and provide methods for safely retrieving both
NewValue in the right formats.
In fact, the change list is actually stored as a
Dictionary<T, PropertyChangedEventData<T>> rather than as a list. This means that changes are indexed and accessed by
PropertyId. It also means that multiple changes to the same property are automatically amalgamated, keeping the first
OldValue and the last
NewValue, and the list does not keep expanding if inadvertently left in accumulation mode for extended periods.
All of the functionality is wrapped in a generic
PropertyChangedEventManager<T> class that does the real work of managing the change list, data type conversions, accumulating and aggregating changes during temporary suspensions and notifying listeners.
Whilst conceptually quite simple, some of the code can get pretty ugly due both to the use of generics and the amount of boilerplate required, which is why I think there must be a better way.
PropertyChangedEventManager<T> class, the boilerplate code for a notifying property within a class is typically reduced to the following:
Where the class has versions of the following definitions:
To register as a listener, any other class simply uses:
Handle_OnPropertyChanged method might look like the following:
Iterating over all changes in a change list can be done using:
Tom MacKnight 9 April, 2015 - 21:38
I just happened across your site and read this post. Since it was written a few years ago I’m sure you’ve settled into a solution.
I don’t program as much as I used to, but in writing classes I began using a checksum to evaluate the current state. That way users could randomly enter information via the interface and as Set/Puts were issued to the class from the interface values would be ANDed to the checksum. Once the checksum reached a goal value then the class had all of the information required to perform its analysis or action.
So for something like sun angles, Longitude = 2, Latitude = 4, DayOfYear = 8, TimeOfDay = 16. Anytime one of the properties are set, the checksum would be reviewed to see if it had reached the goal state/value. When the goal state was reached an event would kickoff, the analysis would take place, kick-off a callback, and the interface could update the control to draw the graph or whatever.
I’ve also used this in conjunction with a messaging system similar to the one Windows uses to create a state engine.
Click here to comment on this page.