-
Notifications
You must be signed in to change notification settings - Fork 129
Implementing and usage of INotifyPropertyChanged
The implementation of the INotifyPropertyChanged interface is a topic that produced much discussions in the .NET community. Several different approaches can be found in the internet or in various Frameworks (e.g. MVVM Frameworks). This article provides a summary of the different approaches and identifies its strength and weaknesses. Additionally, it gives an outlook in which direction Microsoft is heading to provide an additional solution.
The INotifyPropertyChanged interface is simple. It defines just the PropertyChanged
event. But the implementer should consider the following scenarios. They are shown via a plain straightforward implementation. The second part of this article shows different approaches with their strength and weaknesses.
This is the basic implementation that just considers the Framework Design Guidelines regarding implementing Events.
public abstract class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) { PropertyChanged(this, e); }
}
}
The most common scenario is to raise the event when a property setter is called. The event is raised only when the property value has changed. This helps to avoid stack overflows when properties influence each other.
public class Person : Model
{
private double height;
private double weight;
public double Height
{
get { return height; }
set
{
if (height != value)
{
height = value;
RaisePropertyChanged("Height");
}
}
}
public double Weight
{
get { return weight; }
set
{
if (weight != value)
{
weight = value;
RaisePropertyChanged("Weight");
}
}
}
}
Sometimes a property does not have a setter. The property value could still change without a setter. In such a case it is necessary to raise the PropertyChanged event as well. These properties are also known as calculated properties.
public class Patient : Person
{
public double BodyMassIndex
{
get { return Weight / (Height * Height); }
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == "Weight" || e.PropertyName == "Height")
{
RaisePropertyChanged("BodyMassIndex");
}
}
}
The example above shows that these properties could be separated in sub classes. This way it is necessary to listen for the PropertyChanged calls of the base class to be informed when a dependent property has changed.
This straight-forward implementation is simple to understand and easy to implement. But it comes with a major drawback. 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 is correct.
Microsoft introduced a new compiler feature in C# 5.0. It allows the usage of the CallerMemberNameAttribute to generate the property name during compile time. The implementation shows how this looks like.
public abstract class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{
if (object.Equals(field, value)) { return false; }
field = value;
RaisePropertyChanged(propertyName);
return true;
}
protected void RaisePropertyChanged(
[CallerMemberName] string propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) { PropertyChanged(this, e); }
}
}
If the optional parameter propertyName
is omitted then the compiler inserts the member name of the caller. The usage of the former base class is shown by the next example.
public class Person : Model
{
private double height;
private double weight;
public double Height
{
get { return height; }
set
{
if (height != value)
{
height = value;
RaisePropertyChanged();
}
}
}
public double Weight
{
get { return weight; }
set { SetProperty(ref weight, value); }
}
}
This approach solves the issue with the property name string partly. We do not need to pass the property name anymore when the property setter raises the event. But if we have code that wants to listen to a specific PropertyChanged event then we still need to use a property name string.
C# version 6.0 comes with the new keyword nameof
that solves the open issue. With this keyword we do not need to work with unsafe property name strings anymore.
public class Patient : Person
{
public double BodyMassIndex
{
get { return Weight / (Height * Height); }
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(Weight)
|| e.PropertyName == nameof(Height))
{
RaisePropertyChanged(nameof(BodyMassIndex));
}
}
}
This is my preferred approach and that’s why the WPF Application Framework has implemented this solution (see Model
class).
This implementation is widely used. It allows to specify the relevant property via lambda syntax.
public double Height
{
get { return height; }
set
{
if (height != value)
{
height = value;
RaisePropertyChanged<Person>(x => x.Height);
}
}
}
The advantage of this solution is that it works with older .NET Framework versions as well. It doesn’t require C# 5.0. On the con side is the performance. The evaluation of the expression tree is done at runtime. This might have a visible negative impact on the applications performance.
WAF uses this approach in the unit test extensions. The AssertHelper.PropertyChangedEvent
method allows to check that a PropertyChanged event is raised during the execution of an action.
The code-weaving approach modifies the compiler generated IL-code so that the raising of the PropertyChanged event is done automatically. This works for auto-properties as well. Some solutions require that the type or property is marked by a specific attribute (e.g. [NotifyPropertyChanged]) so that the code-weaver knows which properties have to be modified.
[NotifyPropertyChanged]
public double Weight { get; set; }
Code-weaving requires the adaption of the build process. Furthermore, the debugger is not able to show what is really going on within the raise property changed event code because it has no source code for it.
Example: The code-weaver PostSharp could be used for this.
The next generation .NET compiler (aka. Roslyn) allows to do this without the need for a 3rd party component. These compilers are part of Visual Studio 2015.
The next approach discussed in this article is the implementation via proxy generation. A framework creates dynamic proxies of the objects during runtime which extends the objects in a way that the PropertyChanged event is raised automatically.
Proxy generation comes with some major limitations. The classes must define all properties as virtual and they must not be sealed. Additionally, the proxy generation is done at runtime which might have a negative impact on the application’s performance.
Unfortunately, I have seen implementations that use the StackTrace class to retrieve the property name. This does not work correct for various reasons.
// 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));
}
}
- 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.