
As I expected (I saw many debates on INotifyPropertyChanged on the web) I got some reactions to my previous post and one of them by Willem who pointed out a construction that I forgot to mention and is very elegant.
Instead of using reflection we can also use Linq expressions to refer to methods and properties and this allows us to make the following construction:
public class Person : INotifyPropertyChanged
{
#region FirstName Property
private string _firstName;
public string FirstName
{
get
{
return _firstName;
}
set
{
if (_firstName != value)
{
_firstName = value;
NotifyPropertyChanged(x => x.FirstName);
}
}
}
#endregion
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<TProperty>(
Expression<Func<Person, TProperty>> propertyExpression)
{
MemberExpression memberExpression = propertyExpression.Body
as System.Linq.Expressions.MemberExpression;
if (memberExpression == null || (memberExpression != null &&
memberExpression.Member.MemberType != MemberTypes.Property))
{
throw new ArgumentException(
"The specified expression does not represent a valid property");
}
OnPropertyChanged(memberExpression.Member.Name);
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Things to note:
- Full renaming support without needing to remember to update the string.
- A slight performance penalty but better than reflection (a good place to get up to speed with .
You could put these in a base class but I want to be able to pick any base class I want so I made a new template and a new snippet. Download them here.
Copy the snippet to: <drive>:Users<user>DocumentsVisual Studio 2010Code SnippetsVisual C#My Code Snippets
Copy the zipped template to: <drive>:Users<user>DocumentsVisual Studio 2010TemplatesItemTemplatesVisual C#
6 comments
I thought you could make an even more elegant solution to this by implementing both NotifyPropertyChanged and OnPropertyChanged as extension methods on the INotifyPropertyChanged interface. That way you wouldn’t have to inherit from a base class. But the problem is that from the extension methods you do not have access to the PropertyChangedEvent. It is on the interface, but you cannot get the underlying PropertyChangedEventHandler delegate instance.
Jonathan Mezach
You can create an extension method on PropertyChangedEventHandler and raise the event using something like this:(this, () => this.Name);
PropertyChanged.Raise
Extension method
///
///
static class PropertyChangedEventHandlerExtensions
{
///
///
///The type of the property that changed (this PropertyChangedEventHandler handler> property)
/// The PropertyChanged event
/// The object of which the property changed
/// /// Expression tree of the property that changed
/// e.g. ‘() => this.PropertyName’
///
public static void Raise
, object sender, Expression
{
PropertyChangedEventHandler temp = handler;
if (temp != null)
{
MemberExpression me = property.Body as MemberExpression;
if (me != null)
{
temp(sender, new PropertyChangedEventArgs(me.Member.Name));
}
}
}
}
Ronald Bosma
Indeed, in extension methods you do not have access to private fields. And that is good; if it was possible to access these you would be able to write code that depends on class internal thereby breaking encapsulation.
Erno de Weerd
Indeed that is possible. What I dislike about this is that the extension does not stick to the regular pattern of providing a protected method that raises the event. But it is a nice idea. Thanks!
Erno de Weerd
Would it not be more correct in this implementation to also make the ‘general’ OnPropertyChanged method private? Or is this just a way to leave it open to enable a refresh of all properties by parsing an empty string as property name?
Just a thought.
Meile Zetstra
Meile,
The reason for making it protected (as with any event raiser) is to enable deriving classes to use this method to raise the event when needed. Making it private would make that impossible.
As for raising a general PropertyCanged event with an empty string; although it is possible it is not according to the specs I guess.
Erno de Weerd