Sunday, January 3, 2016

The Evolution of INotifyPropertyChanged

When we implement the INotifyPropertyChanged interface, it's pretty common for us to copy code from a previous implementation or use a shared base class. What we don't normally do is review the code that we've been reusing.

But there are some features in .NET 4.5 and 4.6 that we can use to improve our implementation. Let's take a look at those to see how we can reduce our code without reducing functionality or safety.

Update: As Andrew points out in the comments; this article previously used the incorrect terminology for the null-conditional operator "?.". This has been corrected.

A Bit of Background
I really love data binding in XAML; it's extremely flexible and powerful. But in order for data binding to work appropriately, we need to implement the INotifyPropertyChanged interface in our data classes.

Original Implementation
The INotifyPropertyChanged interface only has a single member: the PropertyChanged event. To make it easier to raise this event, it's pretty common to create a helper method. Common names for this are "OnPropertyChanged" or "RaisePropertyChanged" (I prefer "RaisePropertyChanged").

So here's an implementation that I use to use:

Original Implementation (could be better)

This shows our helper method (RaisePropertyChanged) that takes the property name as a string. This is used to create the event args and then invoke the event.

This notifies the UI that anything bound to this property needs to be updated. If the event is called with a "null" parameter, then it is the same as notifying that all the properties have been updated.

To raise the event, we call "RaisePropertyChanged" in the setter for our properties:

Original Property Implementation (pre .NET 4.5)

If the value of the property has changed, then we set the property and then raise the event using a string value for the parameter.

Safer Implementation
This code worked, but it can be improved. First, let's look at it from a thread-safety perspective. It's possible that the PropertyChanged event could be updated in between the null check and the code to invoke the event.

To make things safer, we should use an intermediate variable:

Safer Implementation (pre .NET 4.5)

By using the "handler" variable, we create a copy the PropertyChanged event. Since this is a local variable, we know that it will not be updated in between the null check and the invocation of the event.

This gives us a thread-safe implementation of our "RaisePropertyChanged" method. (Again, thanks to Brian Lagunas for pointing this out to me.)

Improvements with .NET 4.5
The previous code works, and it will continue to work, but it does have some shortcomings. First is the use of the string parameter.

Original Property Implementation (pre .NET 4.5)

Hard-coded strings are rarely a good idea. If we happen to type the string incorrectly, then the data binding will not work as expected. This is especially frustrating to debug because there are no runtime errors; the UI elements are simply not updated like we want them to be.

In addition, if we change the name of our property, we need to make sure to update the string as well. Refactoring tools have gotten better; many of them have the option to update strings and comments when we rename a property. But it's possible to miss updating the string parameter.

Using the CallerMemberName Attribute
In .NET 4.5, we got a new attribute that can help us with our string problem: CallerMemberName. You can get a full description of how it works here: Using the CallerMemberName Attribute for Better XAML Data Binding.

The short version is that we can add the attribute to our INotifyPropertyChanged implementation:

Implementation with CallerMemberName Attribute (.NET 4.5)

Notice the attribute before the parameter. This value will automatically be populated with the name of the object calling this method.

Note: This is the non-thread-safe version of the code (it's taken from the example code in the article mentioned above). We should add the intermediate variable to make things safer.

With this new attribute in place, we can now update our properties as follows:

Property Implementation with CallerMemberName (.NET 4.5)

Notice that when we call "RaisePropertyChanged", we no longer pass in a parameter. Instead, we rely on the fact that "ProgressPercentage" (the name of our property) will automatically be passed because of the "CallerMemberName" attribute.

This gives us the advantage of no longer having a hard-coded string in our code. So we don't have to worry about typos or missed updates if we rename things.

Improvements with Visual Studio 2015
The previous code works (and will continue to work), but there are a couple of new features that came in with C# 6 that we can use. Since this is language/compiler related, we can use these with earlier version of .NET as long as we use the Visual Studio 2015 compiler to build the code.

"nameof" Expression
The "nameof" expression can get us the string name of a variable, type, or member. This is new in C# 6. Check out MSDN for more information: nameof (C# and Visual Basic Reference).

This lets us create our properties like this:

Property Implementation with "nameof" Expression (.NET 4.6)

Instead of passing a hard-coded string to "RaisePropertyChanged", we use "nameof" to generate the string for us. The end result is the same: we will pass the string "ProgressPercentage" to our method. The difference is that we will get a compile-time warning if we typo "ProgressPercentage" since this references the property.

In addition, if we use a refactoring tool to rename our property, the reference in "nameof" will get updated as well.

Using "nameof" will work with or without the "CallerMemberName" attribute.

Null-Conditional Operator
Another feature of C# 6 that we can use is the null-conditional operator (also called the "Elvis operator"). This takes of the form of "?.", and it combines a method or property reference with a null check.

Let's look at our INotifyPropertyChanged implementation to see how this operator works:

Original Implementation (could be better)

Compared to:

Implementation with "?." (.NET 4.6)
If the "PropertyChanged" event is null (meaning there are no event handlers hooked up to it), then everything after the "?." is ignored.

If "PropertyChanged" is not null, then the "Invoke" method is called. Note: calling "Invoke" explicitly is the equivalent of calling "PropertyChanged" with the parameters in the prior example.

The best thing about the null-conditional operator is that it is thread safe. This means that we no longer need the intermediate variable that we use earlier. So we end up with a very compact method that is thread safe.

Combining Features
We can also combine the "CallerMemberName" attribute with the null-conditional operator. Whether we do this depends on how we want to do our property code: either using no parameter or the "nameof" expression.

Reviewing Implementations
It's really easy for us to keep using the code that we're comfortable with. The "INotifyPropertyChanged" interface is especially prone to this. It is treated like boiler-plate code that has a consistent implementation. Because of this, we don't often review the code (as long as is continues to work).

I've described a very simple implementation here. Often people will use a base class or more involved implementation to make it easier to create properties. You can check out Dan Rigby's series of articles for some options on that.

From time to time, we should take a look at our boiler-plate implementations to see if they can be improved. In the case of "INotifyPropertyChanged", the "nameof" expression makes our code less brittle and easier to maintain. In addition, we can use the null-conditional operator to improve readability while still maintaining thread safety.

We'd never know about this without taking time for review.

Happy Coding!

10 comments:

  1. Great post. It's my understanding that you don't want to check the property itself for null and then call it, because it could become null after the check. It would be extremely rare, but a possibility. I've always stored the event to a local variable, check for null, and then use the variable as the event raiser.

    ReplyDelete
    Replies
    1. Yes, you do not want to check the event itself for null (in the original version of the code). But if we use the null-conditional operator "?." we do not need the local variable. This is because the compiler handles that for us (it actually generates a temporary variable). See more here: https://msdn.microsoft.com/en-us/library/dn986595

      Delete
  2. Hi Jeremy! I would say that evolution of INPC implementation has nothing to do with .NET itself. It's more about using new language features.
    C# 5 allows us using [CallerMemberName] (and other Caller* attributes). While these attributes are defined in .NET 4.5 mscorlib we can actually define them in our assemblies targetting .NET 4. Of course in this case we need to use the right namespace namely System.Runtime.CompilerServices.
    The same situation with "nameof" and null-conditional operators. They don't depend on .NET version. They are just new features coming with C# 6.

    ReplyDelete
    Replies
    1. Hi Andrew! The language features/compilers and framework features do evolve independently. I remember when "var" came out with C# 3, and we found we could use it with .NET 2.0 code as long as we compiled with the right version of Visual Studio.
      While I agree with you regarding the C# 6 features ("nameof" and null-conditional operators), I don't think I would say the same about CallerMemberNameAttribute. This class was added in .NET 4.5, and I would be very uncomfortable creating classes in any of the "System" namespaces so that I could use it with .NET 4.0.
      I did spend some time thinking about this for this particular article (and as you point out, I probably didn't take the best route). The best approach is probably to follow the MSDN example: Things that are language/compiler related are marked with the version of Visual Studio; things that are framework related are marked with the framework version. In the docs, CallerMemberNameAttribute is marked with ".NET 4.5", and "nameof" and null-conditional are marked with "Visual Studio 2015".
      Thanks for the comment!

      Delete
  3. And one small addition.
    Null-coalescing operator is "??" (https://msdn.microsoft.com/en-us/library/ms173224.aspx)
    "?." and "?[]" are called null-conditional operators (https://msdn.microsoft.com/en-us/library/dn986595.aspx)

    ReplyDelete
    Replies
    1. Thank you for the correction. I will update the article. I'm surprised you're the first one who caught this.

      Delete
  4. Hi Jeremy, thank you for this great summary article about this evolution. I don't see the point of using nameof() instead of the CallerMemberName though, is it about performance? Thank you and all the best.
    Antoine

    ReplyDelete
    Replies
    1. Hi Antoine, in this case functionality between CallerMemberName and nameof() would be similar. I don't believe there is a performance difference. As a personal choice, I prefer using nameof() because the code is a bit more explicit in what we want. Rather than letting the compiler decide the value (which happens when we use CallerMemberName), we tell it the type we want to use.
      In this simple example, it doesn't make much of a difference. But I've used calculated properties pretty extensively in the applications that I build. In that scenario, I may want to have a property setter call "RaisePropertyChanged" multiple times for multiple properties (meaning, the current property and any calculated properties that are affected by the change).
      For example, if I have Price and Quantity properties on an order form, I will want to update a TotalPrice calculated property if either of those change. So the setter for price would call both "RaisePropertyChanged(nameof(Price))" and "RaisePropertyChanged(nameof(TotalPrice))".
      I hope that makes sense, maybe I need to expand on it in another article.

      Delete
    2. Hi Jeremy, Thank you for this answer. It makes sense indeed. I haven't thought of this use case, but it also happened to me in the past.
      All the best,
      Antoine

      Delete
    3. I wrote up an article about using calculated fields and also looked at the IL for performance differences (there aren't any runtime differences). Thanks again for the question. http://jeremybytes.blogspot.com/2016/02/callermembername-vs-nameof-in.html

      Delete