Monday, October 6, 2014

Using the CallerMemberName Attribute for Better XAML Data Binding

I really like XAML (in case you haven't figured it out yet). The data binding mechanism is pretty awesome -- you can data bind to properties of other controls, data bind to properties of data objects in code, and even data bind to incompatible datatypes using a value converter. I often show this last feature by data binding a background color to a DateTime property -- this let's me have different colored backgrounds for records based on a decade.

But it's not magic. In order to for the data binding to work well with our C# objects, we need to implement the INotifyPropertyChanged interface. It's not a show-stopper, but it is a bit of a hassle. It turns out that we can use the CallerMemberName attribute (which we got in .NET 4.5) to make things a bit better.

Let's look at an example.

Standard INotifyPropertyChanged Implementation
For our example, we'll use a prior example that uses the BackgroundWorkerComponent with the MVVM design pattern. You can get the code sample here: CallerMemberName Attribute Sample.

This is a WPF application where the UI elements are data bound to properties in a view model. Here's what the application looks like when it's running:

The part that we'll be looking at is the progress bar that's right in the middle. The value of the progress bar is data bound to a property called "ProgressPercentage". But in order for the UI to get notified when we change the value in the code, we need to raise a property changed event. This is normally done by implementing the INotifyPropertyChanged interface on the view model.

Here's a typical implementation of INotifyPropertyChanged (this is in the "MainViewModel.cs" file):

Notice that we have a "PropertyChanged" event. This is an event that is specified by the interface. And generally, we also include a method that makes it really easy to raise this event. In this example, this is the "RaisePropertyChanged" method.

And we call this method in the setter of our properties. Here's the "ProgressPercentage" property:

So, if we follow the logic, when we set our property, it calls the "RaisePropertyChanged" method and passes in the name of the property as a string.

Then the "RaisePropertyChanged" method uses that parameter to pass the name of the property in the event args, and it raises the "PropertyChanged" event.

This alerts the UI that the "ProgressPercentage" property has been modified and that any UI elements that are data bound to this property should be updated.

Default Parameter
There's one other thing to notice about our "RaisePropertyChanged" method: it has a default value for the parameter:

This means that if do not pass in any value, the "propertyName" parameter will be null. If we pass "null" on to the PropertyChanged event, then it alerts to UI to update *all* data bound elements.

This is a great way to refresh an entire screen. But it is also very inefficient. If we send update alerts for values that haven't changed, then we're just wasting computation cycles. So we generally want to target our updates as much as possible.

The Problem of String Parameters
There is a bit of a problem with having a string parameter in this case. What happens if we type the name of the property wrong when we call "RaisePropertyChanged"? Nothing happens. Our UI doesn't update as we expect. And we don't get any compile-time checking on this because it's just a string.

We need to think about refactoring as well. What happens if we rename the property? Refactoring tools are good about renaming any references to the property we renamed, but they aren't always good about updating strings that might match the value. (And when the refactoring tools do offer this option, they don't often have it selected by default.)

It's easy enough to create a helper method that allows us to pass in something other than a string. The Prism framework includes an implementation that let's you pass in a lambda expression. This has the advantage of being compiled (so no worries about typos), and it also will get updated automatically by refactoring tools if we rename it.

So that introduces us to the situation. What can we do to improve it?

Using the CallerMemberName Attribute
In .NET 4.5, we got a new attribute: "CallerMemberName". When we use this attribute with a parameter, it will automatically fill in the name of whoever made the method call. The best way to understand this is to add it to our example.

In order to use this attribute, we first need to add a using statement to System.Runtime.CompilerServices:

Here's our updated "RaisePropertyChanged" method:

The only difference is that we added "[CallerMemberName]" before our parameter. Now, if we do not include an explicit parameter, the name of the caller will be used as the default value.

Let's update our "ProgressPercentage" property to remove the string parameter:

Now we no longer have to worry about the "ProgressPercentage" string in our code. If we run our application, we will see that it behaves exactly the same way that it did before.

And to show that we are getting the value that we want, we can set a breakpoint an look at the value that is coming in to the "RaisePropertyChanged" method:

This shows that even though we don't have any parameter in our source code, a value still gets inserted for us.

Updating All Properties
As mentioned above, we can pass in a "null" to notify the UI that all of the properties have been updated. But we can't just rely on the default parameter value anymore. If we leave out the parameter, it will default to the CallerMemberName.

But we can still explicitly pass in a null:

This gives us the results we expect:

So we can still update all of the properties if we need to.

Are There Performance Concerns?
When looking at this code, it looks like it's using reflection in order to figure out the value for the parameter. Reflection is slooooow, so this should be a concern. But we don't need to be concerned about this. One hint is that the namespace is "System.Runtime.CompilerServices".

The magic of "CallerMemberName" happens at compile-time. So, the resulting code is no different than if we had included the string explicitly in our code.

Don't believe me? Let's look at the IL that is generated:

This shows that the "RaisePropertyChanged" method is still being called with a static string parameter ("ProgressPercentage"). This means that our compiled code is just as efficient as if we had included the string in our source code.

One Requirement
There is one requirement when we use the "CallerMemberName" attribute with a method parameter: the parameter must have a default value. In our case, we have a default of "null" specified, but the default can be any valid value. The reason for this requirement is that the compiler sticks in the parameter value from the attribute in a similar way to how default parameter values are handled. So this will only work if there is a default for the parameter.

Wrap Up
We can use the "CallerMemberName" attribute to make our implementation of INotifyPropertyChanged much better. Instead of passing in hard-coded strings, we let the compiler fill in the value for us.

This eliminates the problem of having typos in the parameter or having incorrect values after renaming a property. This is especially frustrating with XAML data binding because we would not get any errors telling us what is wrong -- the binding simply would not work the way that we expect it to.

I like to eliminate these types of problems wherever possible. It turns out that the "CallerMemberName" attribute can help us with this.

Happy Coding!

1 comment:

  1. Huge respect.
    I sheldom find such well structured articles, and yours covered this specific subject completely.
    Everything is explained perfectly, and connected conceptually