Have you ever been annoyed with having to implement the cumbersome plumbing required for INotifyPropertyChanged ? Well, I have. So I tried to find a way to make authoring bindable objects better.
The typical example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Contact : INotifyPropertyChanged { private string _firstName; public string FirstName { get { return _firstName; } set { if (!Equals(_firstName, value)) { _firstName = value; OnPropertyChanged("FirstName"); } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } |
As you can see, it is quite verbose. The event and OnPropertyChanged() method need to be implemented for each class and it’s easy to get the implementation of OnPropertyChanged() wrong (typically introducing a race condition). Moreover, it’s 10 lines of code for each property. I hate that. The more you have to write, the bigger a surface for bugs to appear. A property should just boil down to public string FirstName { get; set; } .
That won’t be possible of course. The default setters and getters just handle the assignment and loading of a compiler-generated backing field. So we somehow need to add the code for each property.
Enter Bindable objects
Similar to class NotificationObject, Bindable is here to help implementing INotifyPropertyChanged. The NotificationObject class only implements RaisePropertyChanged() but does not help with the implementation of the properties. Here is what you can do with Bindable:
1 2 3 4 5 6 |
public class Contact : Bindable { public string FirstName { get { return Get<string>(); } set { Set(value); } } } |
Noticeably shorter, isn’t it ?
Behind the scene, Bindable uses a dictionary to store the property values. You probably have noticed that the property name is not given to Bindable.Get() or Bindable.Set(). Bindable leverages the compiler to provide the value automatically:
1 2 |
protected T Get<T>([CallerMemberName] string name = null) { } protected void Set<T>(T value, [CallerMemberName] string name = null) { } |
CallerMemberNameAttribute when applied on an optional parameter instructs the compiler to pass a string whose value is the name of the calling member. So when property FirstName calls Get<string>() , the compiler generates code for Get<string>("FirstName") .
Here is the actual code for class Bindable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; namespace TiMoch.Framework { /// <summary> /// Base class to implement object that can be bound to /// </summary> public class Bindable : INotifyPropertyChanged { private Dictionary<string, object> _properties = new Dictionary<string, object>(); /// <summary> /// Gets the value of a property /// </summary> /// <typeparam name="T"></typeparam> /// <param name="name"></param> /// <returns></returns> protected T Get<T>([CallerMemberName] string name = null) { Debug.Assert(name != null, "name != null"); object value = null; if (_properties.TryGetValue(name, out value)) return value == null ? default(T) : (T)value; return default(T); } /// <summary> /// Sets the value of a property /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <param name="name"></param> protected void Set<T>(T value, [CallerMemberName] string name = null) { Debug.Assert(name != null, "name != null"); if (Equals(value, Get<T>(name))) return; _properties[name] = value; OnPropertyChanged(name); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } } |
Some aspects can be improved. For example, enabling subclasses to provide their own backing field. This is left as an exercise to the reader
I use this class a lot when implementing MVVM either in Wpf or Winforms.
What do you think ?
Edit: fixed code snippets as per Krumelur’s comments. Thanks Krumelur.
B-R-I-L-L-I-A-N-T idea! However: I cannot get it to build I tried VS2012 and Xamarin Studio.
In the Set I’m getting:
Error 1 The best overloaded method match for ‘Set(string, string)’ has some invalid arguments
Error 2 Argument 2: cannot convert from ‘T’ to ‘string’
Oh, just noticed: you’re missing code. That’s causing the build error. Set should probably be:
protected void Set(T value, [CallerMemberName] string name = null) {
Debug.Assert(name != null, “name != null”);
if (Equals (value, Get (name)))
{
return;
}
_properties[name] = value;
OnPropertyChanged(name);
}
Krumelur, you are right. Apparently, I used a version of the code before I refactored it. I hit the same issue myself.
Btw, sorry for the late reply, I was enjoying some hollidays
This is really cool I extended it to support IEditableObject using a second dictionary as a memento. That way I can cancel/save changes in winforms grids and similar.
Its a shame there isn’t a [CallerMemberType] attribute we could use that could get us to just Get(); rather than Get<string>();
Applying memento here is a great idea. One could also easily implement undo/redo using snapshots or generate diffs for auditing/logging purposes. I think I’ll write a couple of posts on that. It’s been a while …
Wow! Two thumbs up. This will become a part of every program I write from now on!
Great work, and thank you.