Contents
Introduction
This article shows how to overcome one of the limitations of property change notifications – namely, their inability to recognise property dependencies and propagate them when several properties depend on one another. The end result is a Visual Studio add-in that uses static analysis (via the Cecil library) to identify property dependencies and generate the suitable code-behind.
The Problem
Consider a class describing a person who can vote. The voter has two properties – Age
and CanVote
, the assumption being that you need to be at least 16 to vote. Here’s how it would look in code:
class Voter
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
public bool CanVote
{
get
{
return Age >= 16;
}
}
}
Imagine now that you want to bind both properties to some sort of UI. You change your class to implement the INotifyPropertyChanged
interface, and end up with the following:
class Voter : INotifyPropertyChanged
{
private int age;
public int Age
{
get { return age; }
set
{
age = value;
NotifyPropertyChanged("Age");
}
}
public bool CanVote
{
get
{
return Age >= 16;
}
}
}
Now, you can bind the Age
property to the UI, or just listen to its changes. But, what about CanVote
? After all, when Age
changes, CanVote
should also change. But, there isn’t any place for us to put the NotifyPropertyChanged()
call, because CanVote
has no setter. What can we do?
In a simple scenario, we would simply alter the Age
property to do notifications for both, as follows:
public int Age
{
get { return age; }
set
{
age = value;
NotifyPropertyChanged("Age");
NotifyPropertyChanged("CanVote");
}
}
As you can see, such a solution is error-prone and doesn’t scale. Without some automation mechanism (e.g., code generation), it would be hell to maintain. Also, it’s simply bad practice: you end up having properties which concern themselves with other properties’ notifications. That’s not right! But, how can we fix it?
The Solution
Class Changes
Let’s hypothesize for a moment that we already have a property dependency graph. How would we implement it in C# with respect to our Voter
class? Well, the first thing to do is declare the class partial
and remove the full NotifyPropertyChanged()
implementation, leaving instead just a partial definition. (We also remove the spurious notification.)
partial class Voter
{
private int age;
public int Age
{
get { return age; }
set
{
age = value;
NotifyPropertyChanged("Age");
}
}
public bool CanVote
{
get
{
return Age >= 16;
}
}
partial void NotifyPropertyChanged(string propertyName);
}
So, we now have a slimmer Voter
class, from which we removed the NotifyPropertyChanged()
implementation, since it’s not powerful enough to handle dependencies. Also, I removed the PropertyChanged
event – you’ll see it in a moment. Now, since we made the class partial
, let’s think about what our code-behind class would contain?
Code-Behind Class
The first obvious thing is some sort of a dependency list, specifying which property depends on which. My approach is to simply define it as a Dictionary
:
protected Dictionary<string, string[]> dependencies;
Now that we have a Dictionary
, we also define a function for initializing it with our data:
private void InitializeDependencies()
{
dependencies = new Dictionary<string, string[]>();
dependencies.Add("Age", new [] { "CanVote" });
}
Finally, we implement the partial
NotifyPropertyChanged()
function to take advantage of our Dictionary
. In this implementation, we simply lazy-init the Dictionary
, then use it whenever we need it. Please note there is no circular dependency check.
partial void NotifyPropertyChanged(string propertyName)
{
if (dependencies == null) InitializeDependencies();
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
if (dependencies.ContainsKey(propertyName))
foreach (string s in dependencies[propertyName])
NotifyPropertyChanged(s);
}
}
Finally, we also add the event to this class. After all, our original Voter
class no longer implements the INotifyPropertyChanged
– the partial part does!
Analyser
What I described just now may seem like magic. Who, apart from the developer, would know that the CanVote
property can depend on the Age
property? In actual fact, this information is compiled into IL, and we can extract it using a library like Cecil1. The source code for the analyser is attached, but here is a brief description of how the analyser finds the information:
- First, we locate the compiled assembly, and determine the class and namespace of the selected file.
- Then, we get a list of dependencies by looking at the IL and identifying which property is using which other property. Property
get
s are easy to find – all we have to do is look for methods starting with get_
.
- When we get a list of dependencies, we need to reverse it, since we need a list of effects. What I mean is that, initially, we get a list of the types where A depends on B (A ← B), but our dictionary needs lists specifying that B affects A (B → A).
- The resulting data is emitted into a code-behind of the original class using a template.
Conclusion
This article has demonstrated how to auto-generate property dependency maps. Unlike my previous articles2 3, this generator uses IL analysis rather than source code analysis. Admittedly, such analysers are much less suited for the kind of code generation I did here, but they are powerful, and useful for analyzing assemblies written in languages other than C#.
References
- A free library for IL parsing; available at http://www.mono-project.com/Cecil
- A tool for making C# decorators/proxies, http://www.codeproject.com/KB/codegen/decorators.aspx
- A tool for making C# visitors, http://www.codeproject.com/KB/codegen/visitortool.aspx