Coordinator
Apr 18, 2009 at 7:52 PM
Edited Apr 23, 2009 at 6:50 PM
|
I believe that the INotifyPropertyChanged interface is one of the most important interfaces the .NET Framework provides. Although, the interface defines just one event it isn’t that easy to implement it right. I have seen various approaches to implement
this interface and everyone has its own drawbacks.
I would like to discuss some of the approaches I have seen here. Let’s start with the one I have seen most.
Approach 1: Common implementation
internal class Person1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Characteristics
(+) Easy to implement and use.
(+) The event is raised only when the property name has changed. This helps to avoid stack overflows when properties depend on each other.
(-) The property name is defined as string constant inside the code. This is not refactoring save and it is error prone because the compiler doesn’t check if the property name string is correct.
(-) If a property changes frequently and performance is essential then it might be a problem that every property change creates a new EventArgs object.
(-) The OnPropertyChanged method doesn’t follow the Microsoft Design Guidelines (http://msdn.microsoft.com/en-us/library/ms229011.aspx) because this method should have an argument of the type
PropertyChangedEventArgs instead of string. One of the reasons for this rule can be seen in Approach 2.
Approach 2: Cached PropertyChangedEventArgs
internal class Person2 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static readonly PropertyChangedEventArgs NameEventArgs = new PropertyChangedEventArgs("Name");
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged(NameEventArgs);
}
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) { PropertyChanged(this, e); }
}
}
Characteristics
(+) The event is raised only when the property name has changed. This helps to avoid stack overflows when properties depend on each other.
(+) The PropertyChangedEventArgs are created once per class and not for every property change. This improves the performance.
(+) The OnPropertyChanged method follows the Microsoft Design Guidelines (http://msdn.microsoft.com/en-us/library/ms229011.aspx). This allows us to cache the PropertyChangedEventArgs
to be reused for every property change. Furthermore, a subclass would be able to pass a derived PropertyChangedEventArgs type through this method. By example this derived class might include the old and new value of the property change operation.
(-) The property name is defined as string constant inside the code. This is not refactoring save and it is error prone because the compiler doesn’t check if the property name string is correct.
(-) Every property needs a static field for the PropertyChangedEventArgs.
Approach 3: PropertyName check at runtime
internal class Person3 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
CheckPropertyName(propertyName);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[Conditional("DEBUG")]
private void CheckPropertyName(string propertyName)
{
PropertyDescriptor propertyDescriptor = TypeDescriptor.GetProperties(this)[propertyName];
if (propertyDescriptor == null)
{
throw new InvalidOperationException(string.Format(null,
"The property with the propertyName '{0}' doesn't exist.", propertyName));
}
}
}
Characteristics
(+) The event is raised only when the property name has changed. This helps to avoid stack overflows when properties depend on each other.
(+) The OnPropertyChanged method checks if the passed property name is valid. This helps a lot to find wrong property names which often results from typos or code refactoring. However, the check costs a lot performance and so it is done only
in DEBUG mode.
(-) If a property changes frequently and performance is essential then it might be a problem that every property change creates a new EventArgs object.
(-) The OnPropertyChanged method doesn’t follow the Microsoft Design Guidelines (http://msdn.microsoft.com/en-us/library/ms229011.aspx) because this method should have an argument of the type
PropertyChangedEventArgs instead of string. One of the reasons for this rule can be seen in Approach 2.
Approach 4: Retrieve the property name through a lambda expression
internal class Person4 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static readonly string NamePropertyName = TypeManager.GetProperty<Person4>(x => x.Name).Name;
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged(NamePropertyName);
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Note: The implementation of TypeManager.GetProperty can be seen in the code download.
Characteristics
(+) The event is raised only when the property name has changed. This helps to avoid stack overflows when properties depend on each other.
(+) The TypeManager.GetProperty retrieves the property name through the lambda expression. The lambda expression is type-safe.
(-) The lambda expression uses reflection internally to retrieve the property. This increases the working set (memory usage) of the application and it slows down when the class is accessed the first time.
(-) The usage of the static TypeManager.GetProperty method is not straight-forward.
(-) If a property changes frequently and performance is essential then it might be a problem that every property change creates a new EventArgs object.
(-) The OnPropertyChanged method doesn’t follow the Microsoft Design Guidelines (http://msdn.microsoft.com/en-us/library/ms229011.aspx) because this method should have an argument of the type
PropertyChangedEventArgs instead of string. One of the reasons for this rule can be seen in Approach 2.
Approach 5: Do not use the StackTrace
// DO NOT USE THIS!
protected void OnPropertyChanged()
{
StackTrace s = new StackTrace(1, false);
string propertyName = s.GetFrame(0).GetMethod().Name.Substring(4);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I have also seen implementations that use the StackTrace class to retrieve the property name. Unfortunately, this doesn’t work correct for various reasons:
- The StackTrace implementation relies on the debug symbols. By default, Debug builds include debug symbols, while Release builds do not. Thus, the application works in Debug mode but doesn’t work in Release mode anymore.
- The StackTrace isn’t designed to be called frequently. The performance would be low if it is used for the property changed implementation.
Code Download:
http://compositeextensions.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=26312
More:
How to implement INotifyPropertyChanged right? (Part 2): Calculated Properties and Unit Testing
|