Click here to Skip to main content
Click here to Skip to main content

Automatic Implementation of INotifyPropertyChanged on POCO Objects

By , 7 Jan 2011
 

Introduction

Using databinding in WPF obliges to use and implement the INotifyPropertyChanged interface. This task is usually boring, and even unsafe since properties have to be specified by name via simple strings. The property by string issue can be solved using lambda expression, so we can find some helper method that allows us some syntax sugar like...

NotifyPropertyChanged((NotifyPropertyChanged(()=>MyProperty);

...but we need to derive our ViewModel from some special class, and in any case we have to write some boring code every time we set a property. In order to avoid writing such things, we can use AOP to intercept property setters, but this usually involves some 3rd party library to add to our package, and sometimes this is an issue. In this article, we will see a solution to this problem using AOP, but so circumstantial that we will need just a few classes to embed in our solution in order to solve the problem.

Background

Using INotifyPropertyChanged the plain vanilla way:

public class Customer :INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return name; }
        set {
                if(value!=name) 
                    OnPropertyChanged("Name");
                 name = value;
             }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

With the AutoNotifyPropertyChange helper class, we can reduce the code above to this:

public class Customer 
{
    public virtual string Name
}

Simpler, isn't it?

In order to have the job done with the simple library we are talking about, the following requirements are mandatory to accomplish the result:

  1. ViewModel class has to be public
  2. Property to notify changes on must be public and virtual

Notification will occur only when property is changed through the public interface, proxy does not know anything about internal backing fields.

If a custom implementation of INotifyPropertyChanged is needed, we can pass to the proxy generator a class implementing INotifyPropertyChanged, but in this case we have to ensure the existence of a public or protected function called OnPropertyChanged(string propertyName) firing the event. The proxy generator assumes the function name and supposes the implementation works by firing the PropertyChange event in a proper and consistent way. Below is a model class that already implements INotifyPropertyChanged:

public class ModelSample:INotifyPropertyChanged
{
    public virtual int MyProperty1 { get; set; }
    public virtual double MyProperty2 { get; set; }
    public virtual DateTime MyProperty3 { get; set; }
    public float NoNotify { get; set; }
    #region INotifyPropertyChanged Members
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    #endregion
    
    /* Since this class already implements INotifyPropertyChanged
     * having this function is mandatory
     */
    protected virtual void OnPropertyChanged(string property)
    {
        if (null != PropertyChanged)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }
}    

The code below derives a class from Customer and wires the code to fire the PropertyChanged event every time we set a property value with a different value. Please note than the TypeFactory class is in a certain sense fault tolerant. If the interface is implemented, implementing the OnPropertyChanged(string propName) is mandatory. We can leverage fault tolerance in order to avoid notification for certain properties: just declare them as non virtual.

Customer model = Activator.CreateInstance(
                TypeFactory.AutoNotifier<Customer>()
                );

How It Works

The AutoNotifyPropertyChange.TypeFactory class uses CodeDom to internally generate a subclass of the model and wire the code in the property setter. Class is internally compiled and returned as a type.

Let's see this example (POCO) class:

public class ClockViewModel
{
    public virtual int Hour { get; set; }
    public virtual int Minute { get; set; }
    public virtual int Second { get; set; }
    public virtual int Millisecond { get; set; }
    public virtual int Centiseconds { get { return Millisecond / 10; } }        
}

and see the autogenerated class (you will never have to use this code, it is just to show what happens behind the scenes):

namespace @__autonotifypropertychanged
{
    
    
    internal class @__autonotifyClockViewModel : Autonotify.Demo.ClockViewModel, 
		System.ComponentModel.INotifyPropertyChanged
    {
        
        public override int Hour
        {
            get
            {
                return base.Hour;
            }
            set
            {
                if ((false == base.Hour.Equals(value)))
                {
                    base.Hour = value;
                    this.OnPropertyChanged("Hour");
                }
            }
        }
        
        public override int Minute
        {
            get
            {
                return base.Minute;
            }
            set
            {
                if ((false == base.Minute.Equals(value)))
                {
                    base.Minute = value;
                    this.OnPropertyChanged("Minute");
                }
            }
        }
        
        public override int Second
        {
            get
            {
                return base.Second;
            }
            set
            {
                if ((false == base.Second.Equals(value)))
                {
                    base.Second = value;
                    this.OnPropertyChanged("Second");
                }
            }
        }
        
        public override int Millisecond
        {
            get
            {
                return base.Millisecond;
            }
            set
            {
                if ((false == base.Millisecond.Equals(value)))
                {
                    base.Millisecond = value;
                    this.OnPropertyChanged("Millisecond");
                    this.OnPropertyChanged("Centiseconds");
                }
            }
        }
        
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        
        protected virtual void OnPropertyChanged(string propertyName)
        {
            System.ComponentModel.PropertyChangedEventHandler handler;
            handler = this.PropertyChanged;
            if ((null != handler))
            {
                handler(this, 
		new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

All the plumbing is done and you obtain a proxy implementing INotifyPropertyChange. You probably noted that the property "Centiseconds" is a read only calculated property, but in the generated code we see the event raising in the correct place: changing the milliseconds force a change to the Centiseconds property too. This is because internally the proxy generator investigates the code for every property getter in order to see if they use other property value, if so the property is considered to be dependent, proper events are fired. The inflector does not look at the code behavior, it simply assumes that if a getter is used then this will influence the result (this is correct while we are not using a property value inside a getter to produce some side effect, in this case a change event will be fired without reason, but in such a situation this will probably not be an issue).

To create the proxy, just write this:

var obj = Activator.CreateInstance( 
AutoNotifyPropertyChange.TypeFactory.AutoNotifier<ClockViewModel>() );

Autogenerated classes are cached internally, so asking many times for the same type does not require the code for the proxy to recreate and rebuild.

Points of Interest

The code is compiled as a DLL, but the classes for the proxy are just a few and can be merged into an existing solution code without any pain, there are no additional dependencies. In the sample application provided, a binding with a simple view is shown, using the ClockViewModel.

History

  • 30th December, 2010: Initial version
  • 4th January, 2011: Article updated

License

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

About the Author

Felice Pollano
Italy Italy
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
SuggestionVery nice work - inside is a markup extension so the DataContext can be easily set in xamlmemberSBendBuckeye14 Feb '13 - 10:45 
You have written an excellent article. We try to always set our DataContexts in code, so I wrote a simple markup extension to enable the same.
using System;
using System.Windows;
using System.ComponentModel;
using System.Windows.Markup;
 
namespace Autonotify.Demo
{
    /// <summary>
    /// Markup extension to allow auto notification to be setup in xaml. Since the class will never actually be
    /// called using standard markup syntax (e.g. {}), we can ignore the best practice naming convention.
    /// </summary>
    public class DataContextGenerator : MarkupExtension
    {
        public Type TargetType { get; set; }
 
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            var host = target.TargetObject as FrameworkElement;
 
            // Check for design mode or the clock will run in the designer.
            if (DesignerProperties.GetIsInDesignMode(host))
            {
                return null;
            }
            else
            {
                return Activator.CreateInstance(AutoNotifyPropertyChange.TypeFactory.AutoNotifier(TargetType));
            }
        }
    }
}
It can then be used in xaml as below:
    xmlns:local="clr-namespace:Autonotify.Demo"
 
    <Window.DataContext>
        <local:DataContextGenerator TargetType="local:ClockViewModel" />     
    </Window.DataContext>
Have a great day!
 
j2associates_NO_SPAM_@yahoo.com

GeneralMy vote of 5membergjvdkamp22 Feb '11 - 5:38 
Very god stuff! Clever implemenatation too.
 
It would be nice if something similar could make it into C#5. Just like we got {get;set;} , we should get sugar like {get;notifyset;} and have the compiler deal with this stuff. Then we wouldn't have to create auxilary classes to deal with this. Creating all this boilerplate stuff is just too cumbersome.
GeneralHere is my take on itmemberSiderite13 Jan '11 - 23:48 
Here is my approach, shamelessly stealing from yours, but using text templates instead of inline codedom: http://siderite.blogspot.com/2011/01/another-take-on-inotifypropertychanged.html[^]
----------
Siderite

GeneralPrivate modelsmemberOleg Shilo11 Jan '11 - 17:44 
Very nice approach.
 
There is a small problem: for the solution to work the ViewModel must be public.
public class ClockViewModel - works
internal class ClockViewModel - does not work
 
It is not guaranteed that all ViewModels are always public.
 
Another potential improvement is to do even more "deep" code generation: generating full ViewModel from the interface for primitive data classes.
 

interface IClockModel
{
    int Hour { get; set; }
    int Minute { get; set; }
    int Second { get; set; }
    int Millisecond { get; set; }
}
...
DataContext = Activator.CreateInstance(TypeFactory.AutoNotifier<IClockModel>());
 
Thank you for sharing.
GeneralRe: Private modelsmemberFelice Pollano12 Jan '11 - 20:33 
Thanks Oleg for the suggestions,
Unfortunately the "non public" constraint cant't be violated, because "de facto" the proxy is a class in another assembly that derives from the original one. I think this is a limitation too with other mayor proxy as DynamicProxy or LinFu. The second one maybe will work only with basic models, but iti would be interesting to see.
GeneralRe: Private modelsmemberOleg Shilo13 Jan '11 - 13:04 
Yes you are right. I completely forgot about this constrain. I actually was facing the same problem once when I was implementing DuckTyping for interfaces... While it is more common for interfaces to be public it is still not guaranteed they are.
 
Cheers,
Oleg
GeneralIt would be nice if the type could be moved between assemblies.memberSiderite10 Jan '11 - 3:04 
I tried your method Thumbs Up | :thumbsup: on a project I did to test the best WPF practices and I met several problems. First of all, there is the possibility that a ViewModel has both constructors without parameters and with parameters. You check for this directly in the line:
if (ci.IsPublic && ci.GetParameters().Length > 0)
. I removed the Length restriction and it worked. Then I had problems with
foreach (var v in AppDomain.CurrentDomain.GetAssemblies()) {
cu.ReferencedAssemblies.Add(v.Location);
}
in .Net 4.0. An exception would be raised if the assembly is dynamic. Luckily, there is a new property on the Assembly class called IsDynamic and I checked for it before referencing assemblies.
 
However, a problem I could not get around was that the generated type remained part of the assembly that generated it. I am working with a project where the Models and ViewModels are in a separate assembly that does not inherit from anything WPF. When I bound to a property of the autogenerated type, the value that came to the Binding object was null. I guess the solution is to run a bit of instantiation code in the application so that it imports the generated type, but at this point it is pretty much too complicated without stuff like "register my viewmodels". Do you see a way out of this?
----------
Siderite

GeneralRe: It would be nice if the type could be moved between assemblies.memberFelice Pollano10 Jan '11 - 8:12 
Thanks Siderite, for the vote and for the question,
Current version should work even with multiparameter constructor ( see the tests ): obviously activator has to be called with the needed parameter for the constructor, let me know about this. For the additional reference, we would probably need to add some more parametre to the typefactory ( you probably did so already ). I just don't add this to keep the code simple.
About the registration... this is, in order to me, more an extension to add in the MVVM framework... what do you think ?
GeneralRe: It would be nice if the type could be moved between assemblies.memberSiderite11 Jan '11 - 0:09 
Well, the implementation of the extension method wouldn't be too complicated, if you know what the problem is, but how can you see what the problem is when the binding just receives a null value and nothing throws any error? I usually move away from things that require you to know gotchas in order to make them work.
 
As for the activator thing, I did a small method in the factory that just returns an instance of a type:
public static T AutoNotifierInstance<T>(params object[] parameters)
    {
      T autoNotifierInstance = (T) Activator.CreateInstance(AutoNotifier<T>(), parameters);
      return autoNotifierInstance;
    }
----------
Siderite

GeneralRe: It would be nice if the type could be moved between assemblies.memberFelice Pollano11 Jan '11 - 2:58 
Method that return the object is a nice improvment, maybe adding some error checking would be a further addition. And you are correct in avoiding gotchas. I will implement a correction with your fix, but I did not get what you want to do in the extension method.
GeneralRe: It would be nice if the type could be moved between assemblies.memberSiderite11 Jan '11 - 3:40 
I think the problem was with my approach, not your code. However I did find another issue:
if ((false == base.DragSource.Equals(value)))
{
  base.DragSource = value;
  this.OnPropertyChanged("DragSource");
}
This is the generated code. Obviously, if DragSource is not set, a NullReferenceException will be thrown and it is thrown. I wonder, is it possible to provide a text file with the template for the properties based on the type and name of the property, and then replace tokens and compile? It would make for a more flexible solution.
----------
Siderite

GeneralRe: It would be nice if the type could be moved between assemblies.memberFelice Pollano11 Jan '11 - 4:17 
I think we could check if value is null just for non value types, should work without an external config.
GeneralRe: It would be nice if the type could be moved between assemblies.memberSiderite11 Jan '11 - 4:07 
The fix for the issue I was describing is to replace the conditionForNotify with this:
var conditionForNotify = new CodeMethodInvokeExpression
                                 {
                                     Method =new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(typeof(object)), "Equals")
                                 };
 
      conditionForNotify.Parameters.Add(new CodePropertySetValueReferenceExpression());
      conditionForNotify.Parameters.Add(new CodePropertyReferenceExpression
      {
        PropertyName = pi.Name,
        TargetObject = new CodeBaseReferenceExpression()
      });
 
And now it works! Yay!
----------
Siderite

GeneralRe: It would be nice if the type could be moved between assemblies.memberFelice Pollano11 Jan '11 - 4:18 
Great,
Thanks for helping improving !
GeneralMy vote of 5memberSiderite10 Jan '11 - 2:54 
Just what I was looking for
QuestionProxyGen - How can you create an anonymous delegate statement in CodeDom?memberEnrique Albert8 Jan '11 - 6:01 
Your ProxyGen class is very interesting, it is a pity that you did not cover it on the article. It has give me an idea to replace a piece of code that I am using at the moment that invokes an anonymous delegate. Is it possible to use CodeDom to generate code that uses anonymous delegates?
 
Thanks
AnswerRe: ProxyGen - How can you create an anonymous delegate statement in CodeDom?memberFelice Pollano8 Jan '11 - 6:44 
Well I dit not cover it since codedom is a little... verbose. Anyway you basically can do anything with CodeDom, so by googling for it you will foind a solution. If not, maybe you were more lucky with Reflection.Emit, or perhaps using a complete proxy library like LinFu or others.
GeneralSuggestion for WPF binding projectsmemberEnrique Albert7 Jan '11 - 16:09 
Good article, just a suggestion.
 
On the OnPropertyChanged method, it would be good to add a checker method on the following lines:
 
        [Conditional("DEBUG")]
        private void VerifyPropertyExists(string propertyName)
        {
            PropertyInfo currentProperty = GetType().GetProperty(propertyName);
            string message = string.Format("Property Name \"{0}\" does not exist in {1}", propertyName, GetType());
            Debug.Assert(currentProperty != null, message);
        }
 
In this way, hopefully the developer is notified that the XAML binding has to been changed if the property name was modified. (You may need to change the message to return something different that GetType as you class name is not a friendly type)
GeneralRe: Suggestion for WPF binding projectsmemberFelice Pollano7 Jan '11 - 22:00 
Thanks you Enrique,
But I did not exactly see where you want to check the property existence. Could you provide an example ?
GeneralRe: Suggestion for WPF binding projectsmemberEnrique Albert8 Jan '11 - 1:17 
Sure, in your example rename the binding for the Hour field to something like:
 
    ...
    <Grid>
        <StackPanel Orientation="Horizontal">
            <TextBox Text="{Binding HourInvalidName}" Width="60"  ></TextBox>
            <TextBox Text="{Binding Minute}" Width="60"></TextBox>
        ...
 
You get the following warning when executing the code:
 
"System.Windows.Data Error: 39 : BindingExpression path error: 'HourInvalidName' property not found on 'object' ''__autonotifyClockViewModel' ..."
 
but with the suggested solution an explicit exception takes place instead that helps to identify the incorrect binding in a quicker fashion.
 
Thanks
GeneralRe: Suggestion for WPF binding projectsmemberFelice Pollano8 Jan '11 - 2:15 
You are correct,
But this is not related to the AOP proxy implementation, maybe implementing ITypeDescriptor could help, but I've never try that direction. Wpf binding just access the propery we ask in the XAML...
GeneralMy vote of 5memberrmx995 Jan '11 - 11:01 
Ottimo lavoro, chiaro & semplice!!
GeneralMy vote of 5memberprasad024 Jan '11 - 4:59 
Nice 5/5
GeneralGood job, but you've fallen into a trapmvpPete O'Hanlon4 Jan '11 - 0:35 
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if ((null != this.PropertyChanged))
            {
                this.PropertyChanged(this, 
		new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
What happens if PropertyChanged is dereferenced between the null check and the raise event, as can easily happen in a multi-tasking environment (hint you'll get a null exception)? This is a notoriously difficult bug to track down (especially here where the class is automagically generated). What you should do is modify the code as follows:
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if ((null != handler))
            {
                handler(this, 
		new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
This way, even if PropertyChanged is dereferenced, the value in handler will not be nullified, so the code will continue to work.
 
Apart from that minor quibble, this is good stuff. Have a 5.

I'm not a stalker, I just know things. Oh by the way, you're out of milk.

Forgive your enemies - it messes with their heads

My blog | My articles | MoXAML PowerToys | Onyx


GeneralRe: Good job, but you've fallen into a trapmemberFelice Pollano4 Jan '11 - 3:26 
Thanks Pete,
This is a really interesting point, I did not think about that. Very good suggestion

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 7 Jan 2011
Article Copyright 2011 by Felice Pollano
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid