Thursday, June 27, 2013

INotifyPropertyChanged implementation

If you created any XAML based application (WPF, Silverlight, Windows Phone, Windows App Store) using MVVM pattern than you are probably familiar with INotifyPropertyChanged interface.  However to implement the interface most developers are faced with two problem that they should be concerned with.  Consider the follow snippet of code:
private string _propertyA;
public string PropertyA
{
    get { return _propertyA; }
    set
    {
        // P1 repetitive code
        if (value == _propertyA) return;
        _propertyA = value;
        // P2 "magic" string
        RaisePropertyChanged("PropertyA");
    }
}

private void OnPropertyChanged(object sender, PropertyChangeEventArg e)
{
 // P2 "magic" string usage
 if (e.PropertyName == "PropertyA")
  HandlePropertyAChanged();
}

There are two basic problem with this implementation. The first problem is the repetitive code, the 3 lines of code inside set method is pretty much the same for majority of the property and in most implementation it is simply copied and pasted everywhere.  The second issue, magic string, is a slightly more challenging problem to deal with. The big deal with the magic string is when code changes and the name of the property changes the programmer has to not only change the string inside the property's setter but everywhere that listens to the PropertyChanged event.

The first problem can be solved by encapsulate the code into a generic method, and there are a few different strategies for dealing with the second problem and we will explore the pros and cons of different solutions. The second problem can be solved by using constant member for the string thus change only need to occur at one place and we gain the refactor support or using reflection. There's also other implementation by leveraging new .Net feature of optional method parameter mark with attribute. What we will do in this article is to explore a few different implementations that attempt to address these two issue and compare the performance of each implementation. As a bonus there is also and implementation that uses weak event handler to address the possibility of memory leak.

Implementations

Simple

The simple implementation is included here as reference. It does use the constant string to represent property name for refactor support.

Setter

The setter class introduce a helper method to set property value and raise property changed event.
protected bool Set<T>(string propertyName, ref T field, T value)
{
    if (field == null || EqualityComparer<T>.Default.Equals(field, value)) { return false; }
    field = value;
    RaisePropertyChanged(propertyName);
    return true;
}
The property setter than simply become
private DateTime _time;
public DateTime Time
{
    get { return _time; }
    set { Set(TimeProperty, ref _time, value); }
}
This help to reduce boilerplate code and make sure the behaviors are consistent.

Delegate Setter

The delegate setter implementation uses Action<T> lambda to update the value so it doesn't have to have the field parameter passed in as reference, the set helper looks like this
protected bool Set<T>(string propertyName, Action<T> setter, T field, T value)
{
    if (field == null || EqualityComparer<T>.Default.Equals(field, value)) { return false; }
    setter(value);
    RaisePropertyChanged(propertyName);
    return true;
}
To use the helper method we would need to create an inner class that holds the property value
private class Inner : IDisplayText
{
    public string DisplayText { get; set; }
    public DateTime Time { get; set; }
    public bool Status { get; set; }
    public int Count { get; set; }
}
private readonly Inner _inner = new Inner();
The property setting would then call the helper method like this
public DateTime Time
{
    get { return _inner.Time; }
    set { Set(TimeProperty, (t) => _inner.Time = t, _inner.Time, value); }
}

Lambda

The lambda method utilize the Expression<Func<T>> to capture the property that's been updated and does not rely on constant member for property name. Again a helper method is introduced to encapsulate the property changed behavior. See conclusion about performance difference between .net 4.0 to .net 4.5.
protected bool Set<T>(Expression<Func<T>> expression, ref T field, T value)
{
    if (field == null || EqualityComparer<T>.Default.Equals(field, value)) { return false; }
    field = value;
    RaisePropertyChanged(GetPropertyName(expression));
    return true;
}

protected string GetPropertyName<T>(Expression<Func<T>> expression)
{
    MemberExpression memberExpression = (MemberExpression)expression.Body;
    return memberExpression.Member.Name;
}

Field

The field implementation introduces a helper class to capture the backing field for the property
protected class Field<T>
{
    public string PropertyName;
    public T Value;

    public Field(string propertyName)
    {
        PropertyName = propertyName;
    }

    public static implicit operator T(Field<T> t)
    {
        return t.Value;
    }
}
Again with a helper method for setting the property
protected bool Set<T>(Field<T> field, T value)
{
    if (field == null || EqualityComparer<T>.Default.Equals(field.Value, value)) return false;
    field.Value = value;
    RaisePropertyChanged(field.PropertyName);
    return true;
}
The definition of a property looks like this
private readonly Field<DateTime> _time = new Field<DateTime>(TimeProperty);
public DateTime Time
{
    get { return _time; }
    set { Set(_time, value); }
}

Lambda Field

Lambda Field implementation looks to improve the runtime performance (see results section) of Lambda implementation by combining Lambda and Field implementation. The Field class definition looks like this
protected class Field<T>
{
    public string PropertyName;
    public T Value;

    public Field(Expression<Func<T>> expression)
    {
        PropertyName = GetPropertyName(expression);
    }

    protected string GetPropertyName<T>(Expression<Func<T>> expression)
    {
        MemberExpression memberExpression = (MemberExpression)expression.Body;
        return memberExpression.Member.Name;
    }

    public static implicit operator T(Field<T> t)
    {
        return t.Value;
    }
}
The helper method is the same as Field implementation and the definition of property is
private readonly Field<DateTime> _time = new Field<DateTime>(() => Time);
public string TimeProperty { get { return _time.PropertyName; } }
public DateTime Time
{
    get { return _time; }
    set { Set(_time, value); }
}

Field2

Field2 is a modified Field implementation to remove constant string for property name. It borrows Lambda implementation's method of determining property name. The constants for property names are changed to static readonly fields of the class and are set at application startup by the static constructor to reduce overhead
public static readonly string TimeProperty;
public static readonly string StatusProperty;
public static readonly string CountProperty;
public static readonly string DisplayTextProperty;

static Field2Model()
{
    var dummy = new Field2Model(0);

    TimeProperty = GetPropertyName(() => dummy.Time);
    StatusProperty = GetPropertyName(() => dummy.Status);
    CountProperty = GetPropertyName(() => dummy.Count);
    DisplayTextProperty = GetPropertyName(() => dummy.DisplayText);
}

private static string GetPropertyName<T>(Expression<Func<T>> expression)
{
    MemberExpression memberExpression = (MemberExpression)expression.Body;
    return memberExpression.Member.Name;
}
The property implementation is the same as Field
private readonly Field<DateTime> _time = new Field<DateTime>(TimeProperty);
public DateTime Time
{
    get { return _time; }
    set { Set(_time, value); }
}

CallerMemberName

This implementation uses a .Net 4.5+ attribute CallerMemberNameAttribtue.  The implementation simplified all the Setter like implementation by adding a optional parameter and mark the parameter with [CallerMemberName].  The attribute cause the compiler to add the function/property's name as string literal to the method call thus the operation is pretty cheap and has about the same speed as setter.
The setter method look like this
protected bool Set(ref T field, T value, [CallerMemberName]string propertyName = "")
{
    if (field == null || EqualityComparer.Default.Equals(field, value)) { return false; }
    field = value;
    RaisePropertyChanged(propertyName);
    return true;
}
The property implementation is similar to setter without the property name as one of the argument
private DateTime _time;
public DateTime Time
{
    get { return _time; }
    set { Set(ref _time, value); }
}

AOP

The last implementation leverages aspect orientated library such as PostSharp or Fody. PostSharp has some example on how to do it here. I used Fody because it was requested and free.

Test Methodology

A test harness was built to bind the models implementing different property changed strategy. The test application binds a collection of models to a grid and them update the models a million times to generate property changed events. A timer is used to measure program execution time, this is not the CPU time, but the result is sufficient for the comparison. The test harness also create 1 to 100000 objects to account for object construction time, as you will see there are implementations where object construction is somewhat expensive.

Results

The values showing in the chart is in milliseconds and account for total construction time and property update time.


Object Count Simple Setter Lambda Field Lambda Field Field 2 Field With WEH Delegate Setter CMN Attribute Fody
1 752 827 1778 958 1104 970 955 942 814 815
10 730 814 1751 956 1105 959 955 936 810 814
100 733 818 1765 955 1103 961 949 938 810 811
1000 731 842 1885 959 1109 956 956 944 814 820
10000 750 874 2745 984 1205 979 981 1022 870 863
100000 837 999 2837 1122 1950 1095 1123 1171 985 998

The execution time for each implementation is pretty much the same across each implementation regardless of object count, the spike up you see for 100,000 object test is the construction cost.

Conclusion

Simple: use this if you don't mind typing or copy and paste
Setter: good implementation for simpler maintenance
Delegate Setter: good implementation if you are wrapping another class such as entity or dto
Lambda: bad performance not recommended. An interesting point here is the runtime went from 7000+ ms to 1700+ ms when project is convert from .net 4.0 to .net 4.5
Field: preferred method of implementation if you need refactor support in event handler, slightly more boiler plate code compare to setter/CallerMemberName implementation
Lambda Field: use this one if you really really want to use Lambda, but it has a performance penalty if your program create lots of objects. CallerMemberName is preferred over this implementation.
Field 2: recommended method if you just hate literal string for some reason but still want refactor support
CallerMemberName: simple to implement and has good performance, the recommended implementation. However you lose the refactor support in event handler.
APO: no boilerplate code, but your lose refactor support and dependency on external libraries.
You can get the source code to the implementations and test harness at github

22 comments:

Anonymous said...

Outstanding work!
Many thanks!

Comphenix said...

It's surprising just how slow the lambda method turned out to be in the benchmark. Obviously, it had to be slower than using a constant string, but a whole order of magitude?

By the way, I don't think you need to actually construct the dummy object in "Field 2". Use a type instead of var, and assign it to null:
Field2Model dummy = null;

That will clearly save some space, but it also saves some complexity in the constructor in case you're using the Property values.

Jean-Yves LAUGEL said...
This comment has been removed by the author.
Jean-Yves LAUGEL said...

You said : "I didn't include [CallerMemberName] implementation it is .Net 4.5 only and the performance is just as bad as Lambda."

I don't think so : CallerMemberName are changed into literal values at compile time, so there is no overhead at runtime.

Peijen said...

Jean-Yves,

I actually did implemented and tested [CallerMemberName], and the way I implemented it, it is just as slow as lambda method. Maybe if I didn't have the optimal implementation, but I used popular implementation proposed by many people on the internet. If you can show me the decompiled code* stating otherwise I am willing to give it another shot.

*if it is converted to literal value at compile time, by decompile the code (reflector,ispy,etc) it should show the literal value as parameter of method call in the caller.

Jean-Yves LAUGEL said...

Thanks for your reply, Peijen.
I asked a "self answered" question on StackOverflow for that, with my test and conclusion :
http://stackoverflow.com/questions/22580623/inotifypropertychanged-is-callermembername-slow-compared-to-alternatives

Combined to your "SETTER" implementation, this can be a pretty good solution.

Cheers, and thanks for your article.

Bruno Juchli said...

Great work, thanks!

Any chance you could benchmark PropertyChanged.Fody (http://www.nuget.org/packages/PropertyChanged.Fody/)?

Thank you

Peijen said...

@Comphenix,

The dummy = null; change did not yield significant difference. If you look closer the code you are referring to is in the static constructor thus it's only ran once per application.

Peijen said...

@JYL,

Thanks for pointing out that I was wrong with [CallerMemberName] performance. I have update the article to reflect that. However it does have a slight downside of losing refactor support, but I guess there are probably ways around it.

Peijen said...

@Bruno,

Updated with Fody implementation

Leroy said...

I don't get why you would lose refactoring support when using fody. I think it's actually a big improvement over having property names in strings.

Lev said...

Great article!
I've downloaded sorces, file TestResult.cs is missing in package.

Peijen said...

@Lev,

I have added TestResult.cs, the file got ignored.

Peijen said...

@Leroy

I am talking about event handler side of thing. Prior to C# 6 you have to handle event as such

void EventHandler(obj sender, PropertyChangedEventArg e)
{
if (e.PropertyName == "MyProperty")
{
// do stuff
}
}

With field implementation you can do

if (e.PropertyName == MyPropertyField.PropertyName) ...

However with C# 6 this just become so much easier

if (e.PropertyName == nameof(MyProperty)) ...

I will probably do a follow up article with cleaned up code base some day.

Anonymous said...

If you want to waste the time of a of people, just leave our code fragments they way they are, and never post working code.

I wasted an hour trying to get your field example to compile.

Peijen said...

@Anonymous

Did you check the whole solution posted to github? It's at the end of the page. What error did you encounter?

Anonymous said...

Could you please clarify why you loose refactor support using CallerMemberName? From my point of view it's the other way around:

Assume you have a property Foo

int Foo { get {return _foo;} set {_foo = value: OnPropertyChanged("Foo")}};

Now assume that you rename the Foo property to MyProperty. Do you got already what i want to mention? No? OnPropertyChanged is not updated! You will still fire a property changed for Foo instead of MyProperty!

int MyProperty{ get {return _foo;} set {_foo = value: OnPropertyChanged("Foo")}};

this will simply fuck up your code within seconds. And you don't get this error at compile time!!! If you have databinding within WPF you not even get a run time error. It simply does not update the UI Item.

I would say if you want to write maintainable code their are only 2 solultions:
- make a base class with OnPropertyChanged using CallerMemberNameAttribute
- or at least the new nameof() operator in the OnPropertyChanged requieres C# 6.0 and is more work as the above solution is more generic.

But maybe you could bring in some light to the dark and explain where you loose refactoring....

Peijen said...

@Anonymouse,

You lose refactoring support at the listener. CallerMemberName or nameof() in the OnPropertyChanged will work perfectly when you change the property's name. However if you have an event handler that's listening to events and do something like

private void PropertyChangedHandler(e, args) {
if (args.PropertyName == "FirstName") { UpdateFullName(); }
else if (args.PropertyName == "Email") { VerfyEmail(); }
}

In the handler you are checking for the name of the property changed and do something with it, this is where you would lose refactor support. However C# 6.0's nameof() feature would help with this.

Bill Smock said...
This comment has been removed by the author.
Bill Smock said...

This is an incredible example for practical use of ref, generics, lambdas, Func<> and Action<> and all to solve one problem. If I ever need to direct someone to a real way of using these, I'm linking this post.

As for your benchmarking and everything, if you decide to revisit this you should take a look at the CIL to see if they really are so different after being compiled.

Blogger said...

Are you looking to make money from your traffic with popunder ads?
In case you are, have you ever used exoClick?

Anonymous said...

Why do a field == null check in the [CMN] Setter? That way I can never assign a value to referencetype properties.