Click here to Skip to main content
11,706,801 members (44,474 online)
Click here to Skip to main content

Property dependency generation in Visual Studio

, 19 Jan 2009 CPOL 20.7K 157 39
Rate this:
Please Sign up or sign in to vote.
A VS add-in to analyse property dependencies in classes.

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;
    }
  }

  // event and firing code omitted
}

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:

// A dictionary of dependencies such that key is the
// independent variable and value is a string[] of dependent variables.
protected Dictionary<string, string[]> dependencies;

Now that we have a Dictionary, we also define a function for initializing it with our data:

// Since a Dictionary cannot be initialized inline, it has to be
// initialized conditionally.
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.

// This notification has to be invoked by any property that
// needs to notify users of its changes.
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:

  1. First, we locate the compiled assembly, and determine the class and namespace of the selected file.
  2. Then, we get a list of dependencies by looking at the IL and identifying which property is using which other property. Property gets are easy to find – all we have to do is look for methods starting with get_.
  3. 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).
  4. 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

  1. A free library for IL parsing; available at http://www.mono-project.com/Cecil
  2. A tool for making C# decorators/proxies, http://www.codeproject.com/KB/codegen/decorators.aspx
  3. A tool for making C# visitors, http://www.codeproject.com/KB/codegen/visitortool.aspx

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dmitri Nеstеruk
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and F#, though I'm open to suggestions.

I'm a Microsoft MVP (Visual C#) since 2009. I run a collective tech blog at DevTalk.net. I use my own editor called TypograFix to typeset articles and blog posts.

Like the article and want this implemented in your product? Got a project that can benefit from Microsoft.Net goodness? Then get in touch!

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 1 Pin
VickyC#20-Jan-09 14:30
memberVickyC#20-Jan-09 14:30 
AnswerRe: My vote of 1 Pin
Dmitri Nesteruk22-Jan-09 3:04
memberDmitri Nesteruk22-Jan-09 3:04 
GeneralRe: My vote of 1 Pin
Sid_ScienceKid3-May-15 23:15
memberSid_ScienceKid3-May-15 23:15 
GeneralGreat idea to reduce coupling Pin
asthalas20-Jan-09 2:00
memberasthalas20-Jan-09 2:00 
GeneralRe: Great idea to reduce coupling Pin
Dmitri Nesteruk20-Jan-09 2:19
memberDmitri Nesteruk20-Jan-09 2:19 
GeneralRe: Great idea to reduce coupling Pin
asthalas20-Jan-09 3:43
memberasthalas20-Jan-09 3:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150819.1 | Last Updated 20 Jan 2009
Article Copyright 2009 by Dmitri Nеstеruk
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid