Click here to Skip to main content
15,867,750 members
Articles / Desktop Programming / WPF

AutoPropertyChanged - Type-safe INotifyPropertyChanged Implementation

Rate me:
Please Sign up or sign in to vote.
3.54/5 (11 votes)
24 May 2010CPOL6 min read 34.8K   227   14   9
Typesafe INotifyPropertyChanged implementation without run-time Reflection and without lambda expressions.

Introduction

I'd guess anyone who delved a little bit deeper into WPF has learned how the class ObservableCollection integrates so smoothly into WPF's databinding.

The problem is, once you start doing simple things like linking a ViewModel's properties to, say, a CheckBox control, you inevitably start to wonder if you really need to implement the INotificationPropertyChanged interface all the time by foot.

To me, one of the biggest drawbacks of implementing this interface directly is that you need to duplicate a property's name as a string, since the PropertyChangedEventArgs parameter needs the property name. Once you start refactoring, better not forget to update all places referencing the property - if you forget one place, you'll pay that back at runtime :-)

Another drawback is the amount of code you have to write each time you add a new class to your ViewModel. Also, usually you construct a "new PropertyChangedEventArgs" object every time any property changes. This impacts the managed heap and reduces performance.

A good summary of existing solutions to this is here (great site as well, by the way).

This article tries - to my knowledge - a new way of getting around the hassle. Using AutoPropertyChanged, you can get around most of the mentioned problems.

The demo project as well as the Utility.AutoPropertyChangedNotification project are both created using Visual Studio 2010 and .NET 4.0. This blog entry describes a way how 2010 solution and project files can be opened in the 2008 version - never tried it myself though.

Using the Code

First of all, the class which wants to expose its properties needs to be derived from the class ImplementsPropertyChanged. This could be a big drawback, but on the other hand, you're free to bind your properties not to your ViewModel directly but to a property of it, so you could as well leave your ViewModel as it is and contain the ImplementsPropertyChanged object instead of deriving directly.

The first step looks like this:

C#
public class ListItem : ImplementsPropertyChanged
{
    public ListItem()
    {
    }
}

Okay, well this does next to nothing. ImplementsPropertyChanged adds a PropertyChangedEventHandler event to the class and provides a default implementation of OnPropertyChanged.

Let's add a property to the class:

C#
public class ListItem : ImplementsPropertyChanged
{
    public ListItem()
    {
    }

    [AutoPropertyChangedNotification]
    public AutoPropertyChanged<ListItem, bool, Property0> IsChecked
    {
        get;
        set;
    }
}

This code wouldn't run yet. You need to initialize IsChecked to get it to work:

C#
public class ListItem : ImplementsPropertyChanged
{
    public ListItem()
    {
        IsChecked = Prop.New(IsChecked, this);
    }

    [AutoPropertyChangedNotification]
    public AutoPropertyChanged<ListItem, bool, Property0> IsChecked
    {
        get;
        set;
    }
}

Done - that's all you need (save calling AutoPropertyInitializer.InitializeProperties() on startup).

The AutoPropertyChanged has a property of its own, Value. Accessing the property from code or XAML would thus look like this:

C#
ListItem myItem = new ListItem();
myItem.IsChecked.Value = true;

You need to keep this in mind when using it - I didn't find a way to workaround this, since indexers need at least one parameter.

The code-behind in the XAML could look like this:

XML
<CheckBox Content="Check this box" Name="checkBox1" 
          IsChecked="{Binding Path=IsChecked.Value}" />

But what's this generic parameter Property0?

That's the not-so-nice aspect of this solution. As I mentioned above, the problem was to tell the .NET runtime to keep the static PropertyChangedEventArgs fields different, and I didn't find a nicer approach.

But still, it works, and if you mess something up (say, using the same PropertyN field for more than just one property), you'll learn this during the one-time initialization phase by catching a DuplicatePropertyFoundException :-).

How does it work?

AutoPropertyChangedNotification

First of all, there is obviously this attribute AutoPropertyChangedNotification. It's implemented without any fancy stuff, just a plain and simple attribute which can be set to properties only:

C#
[AttributeUsage(AttributeTargets.Property)]
public sealed class AutoPropertyChangedNotificationAttribute : Attribute
{
}

Its purpose is to keep the AutoPropertyChanged-properties (easily) identifiable by Reflection - and also a little bit to indicate to the reader that there is a bit more going on in the background.

Prop.New

The static method Prop.New is just a simplification - usually, you'd need to create the properties like this:

C#
IsChecked = new AutoPropertyChanged<ListItem, bool, Property0>(this);

This looks errorprone. Prop.New alleviates this:

C#
public static class Prop
{
    public static AutoPropertyChanged<T0, T1, T2> New<T0, T1, T2>(
           AutoPropertyChanged<T0, T1, T2> field, T0 hostClass) 
           where T0 : ImplementsPropertyChanged
    {
        return new AutoPropertyChanged<T0, T1, T2>(hostClass);
    }
}

Possibly, this doesn't look nice. It quite surely doesn't, but it compiles to quite a few IL instructions and incurs no runtime overhead - and makes the resulting code much more readable.

AutoPropertyChanged

The generic class AutoPropertyChanged is a little bit more involved:

C#
public class AutoPropertyChanged<TBase, TProperty, 
       TPropertyName> where TBase : ImplementsPropertyChanged
{
    public static PropertyChangedEventArgs propertyChanged;
    public AutoPropertyChanged(TBase parent)
    {
        _parent = parent;
    }
    private TBase _parent;
    private TProperty _value;
    public TProperty Value
    {
        get
        {
            return _value;
        }
        set
        {
            _value = value;
            _parent.OnPropertyChanged(propertyChanged);
        }
    }
}

There's the aforementioned static PropertyChangedEventArgs field. As you can see, still nothing special going on here: two dynamic fields (_parent and _value), and the rest is plain-and-simple invoking the event if needed.

Obviously, this introduces a little bit of overhead - the _parent reference is needed for invoking the PropertyChanged event; we wouldn't need this though if we were implementing the IPropertyChangedNotification interface by foot.

This is everything the runtime needs to execute. No Reflection and no lambda expressions until here - just once Reflection is needed, and this happens in the one-time initialisation phase.

AutoPropertyInitializer.InitializeProperties

This static method triggers the one-time initialisation of the properties. Most of the ugliness is contained there. Let's have a look:

C#
// ... Iterating all loaded assemblies,
// enumerating their types omitted for brevity. Rest:
// Set AutoPropertyAttribute static PropertyChangedEventArgs fields
PropertyInfo[] props = type.GetProperties();
foreach (PropertyInfo prop in props)
{
    if (prop.GetCustomAttributes(typeof(
        AutoPropertyChangedNotificationAttribute), false).Length > 0)
    {
        if (!prop.PropertyType.Name.StartsWith("AutoPropertyChanged"))
        {
            throw new AutoPropertyChangedNotificationAttributeIncorrectlyUsedException(
              BuildExceptionMessage(type, prop, "The attribute " + 
              "[AutoPropertyChangedNotification] may only be used " + 
              "if the following property is an AutoPropertyChanged property.") );
        }
        if( prop.PropertyType.GetField("propertyChanged").GetValue(null)!=null )
        {
            throw new DuplicatePropertyFoundException(
              BuildExceptionMessage(type, prop, "AutoPropertyChanged properties " + 
              "must be distinguished within one class by the last template " + 
              "parameter (Property0...Property19)."));
        }
        prop.PropertyType.GetField("propertyChanged").SetValue(null, 
             new System.ComponentModel.PropertyChangedEventArgs(prop.Name));
    }
}

The most interesting line - aside from the exception section - is the line with the SetValue. That's where the static field of AutoPropertyChanged is initialized. And that's it.

The two checks directly above this line pinpoint typical error scenarios.

If you set more than one property of one class to the same PropertyN generic parameter, you'll receive a DuplicatePropertyFoundException. The exception message will describe which class and which property was the cause, so fixing this shouldn't be problematic.

The other situation is if the attribute [AutoPropertyChangedNotification] was set on a property which isn't of the type AutoPropertyChanged. In that case, a AutoPropertyChangedNotificationAttributeIncorrectlyUsedException is thrown (thanks to God, there is auto-completion). You'll also receive a message which is detailed enough to directly point you to the offending property.

The Demo Application

The demo application is greatly inspired by this article (thanks, Philip) - which, by the way, has one of the best TreeView implementations for WPF I ever encountered. I learned quite a lot about WPF by looking closer into this project :-)

It looks like this:

Screenshot of Demo application

On the left hand side, there are some controls which interact with the controls on the right hand side (and vice versa).

The application itself uses the MVVM view model, having one ViewModel (DemoViewModel) and two Views (LeftView and RightView).

Since there's not that much code contained in it, I think it is probably best you explore the source yourself.

Summary

Pro's

  • Low run-time overhead (still more than coding by foot of course):
    • One additional field per property and
    • One reference per property to its parent
  • No dynamic creation of PropertyChangedEventArgs
  • Type safety and by this, refactoring safety
  • Quite easy to use

Con's

  • Property0...PropertyN required to tell different properties apart (but no runtime penalty for this)
  • Needs property exposing classes to be derived from one base class (I'm quite sure you could get rid of the base class, I'm looking into this)

Change log

  • 1.0: Initial public release (2010-05-24)

License

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


Written By
Germany Germany
I am working with computers for quite a long time now, starting with BASIC on an old "Acorn Electron".

Then i continued with C and moved quickly on to C++ with some Java in the mean time.

Now, my personal favorite is C# and i love especially WPF and its versatileness, as well as the TPL. I'm still on a quite steep learning curve, but thanks to this community (among others of course Smile | :) ) i'm doing okay.

Comments and Discussions

 
GeneralThere are other ways... [modified] Pin
Oleg Shilo26-May-10 0:29
Oleg Shilo26-May-10 0:29 
GeneralUse constants rather than literal strings Pin
tonyt25-May-10 21:33
tonyt25-May-10 21:33 
GeneralSome points Pin
Pete O'Hanlon24-May-10 11:10
subeditorPete O'Hanlon24-May-10 11:10 
1. The interface is INotifyPropertyChanged.
2. By doing this, you have forced an implementation to inherit from this class - what happens if it needs to inherit from a different class structure?
3. The creation of a new PropertyChangedEventArgs object has negligible impact in most cases (I've spent a lot of time optimising WPF applications, and this is not an area I've ever worried about). It will be garbage collected at some point and you are worrying about the wrong problem - this is micro optimisation at the wrong point.
4. Your point about the need to refactor based on property changes is taken care of by the expression tree solution.

"WPF has many lovers. It's a veritable porn star!" - Josh Smith

As Braveheart once said, "You can take our freedom but you'll never take our Hobnobs!" - Martin Hughes.


My blog | My articles | MoXAML PowerToys | Onyx



GeneralRe: Some points Pin
WPFanatic24-May-10 11:29
WPFanatic24-May-10 11:29 
GeneralMy vote of 2 Pin
Paulo Zemek24-May-10 10:18
mvaPaulo Zemek24-May-10 10:18 
GeneralRe: My vote of 2 Pin
WPFanatic24-May-10 10:22
WPFanatic24-May-10 10:22 
GeneralRe: My vote of 2 [modified] Pin
Paulo Zemek24-May-10 10:29
mvaPaulo Zemek24-May-10 10:29 
GeneralRe: My vote of 2 Pin
WPFanatic24-May-10 11:13
WPFanatic24-May-10 11:13 
GeneralRe: My vote of 2 Pin
Paulo Zemek25-May-10 7:18
mvaPaulo Zemek25-May-10 7:18 

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

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