Introduction
If you are a .NET developer, especially a WPF developer with MVVM, you must know the importance of the INotifyPropertyChanged
interface. It is a core interface that is used almost every day. So if we refine the base implementation of INotifyPropertyChanged
, even if the improvement is small, it is likely to lead to quite high productivity. The PropertyObservable class is my second implementation for INotifyPropertyChanged
. (You can see the first implementation here) It is designed to improve productivity as much as possible.
Basic INotifyPropertyChanged Implementation
INotifyPropertyChanged
is a very simple interface. It declares only the PropertyChanged
event:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
The PropertyChanged
event is easily implemented with a few lines of code like this:
public class PropertyObservable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = PropertyChanged;
if (propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
But this is not the end of the story. You must write properties that raise the PropertyChanged
event properly when they are changed.
Writing Properties
If you write a property with the basic INotifyPropertyChanged
implementation, it will look like this:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set
{
if (_number == value)
return;
_number = value;
OnPropertyChanged("Number");
}
}
}
Hereafter I’ll call it a raw property. The raw property is not bad but the raw property setter is a little verbose. The PropertyObservable
class provides methods named SetProperty
for this situation. The raw property setter can be rewritten with a SetProperty
method like this:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set { SetProperty(ref _number, value, "Number"); }
}
}
If you use the .NET framework 4.5 or higher, you can omit the property name like this:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set { SetProperty(ref _number, value); }
}
}
Hereafter I will assume that you use the .NET framework 4.5 or higher for brevity. In most cases, the above property implementation is enough. It is fast, and is recommended for all uses. But it requires managing a private field. Besides, in my point of view it looks a little messy when multiple properties are defined with comments:
public partial class Test : PropertyObservable
{
int _number1;
public int Number1
{
get { return _number1; }
set { SetProperty(ref _number1, value); }
}
int _number2;
public int Number2
{
get { return _number2; }
set { SetProperty(ref _number2, value); }
}
}
Anyway, there is another SetProperty
method that does not require a private field. The Number property can be rewritten without a private field like this:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
}
The GetProperty
method retrieves a property value that is stored in an internal storage. The above property implementation is slower than the previous property implementation, but in my point of view, it looks neat and is slightly more productive. The following example shows two properties with comments:
public partial class Test : PropertyObservable
{
public int Number1
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
public int Number2
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
}
If you need to set a default property value, you can use the getDefault
parameter of the GetProperty
method:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(getDefault: () => 7); }
set { SetProperty(value); }
}
}
Checking Property Changes
It is common to run custom code when a property is changed. If you need to know when a property is changed, the most famous way is to handle the PropertyChanged
event:
public partial class Test : PropertyObservable
{
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == "Number")
{
Debug.WriteLine("Number: " + Number);
return;
}
}
}
But the PropertyChanged
event has a flaw. It does not provide the old property value. Therefore PropertyObservable
provides another event named PropertyValueChanged
that provides the old property value:
public partial class Test : PropertyObservable
{
protected override void OnPropertyValueChanged(IPropertyValueChangedEventArgs e)
{
base.OnPropertyValueChanged(e);
if (e.PropertyName == "Number")
{
Debug.WriteLine("Number: {0} -> {1}", e.OldValue, e.NewValue);
return;
}
}
}
But be warned, the event is only raised when you set a property value by a SetProperty
method. Besides, the OldValue
and NewValue
are not type safe.
Sometimes it is reasonable to handle property changes in property setter for code manageability. In the raw Number
property setter, the above code can be rewritten like this:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set
{
if (_number == value)
return;
int oldValue = _number;
_number = value;
OnPropertyChanged("Number");
Debug.WriteLine("Number: {0} -> {1}", oldValue, value);
}
}
}
If you use a SetProperty
method in the property setter, it can be simplified. The SetProperty
methods return true if a property is changed. So, the above code can be rewritten like this:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set
{
int oldValue = Number;
if (SetProperty(value))
Debug.WriteLine("Number: {0} -> {1}", oldValue, value);
}
}
}
Or you can use the onChanged
parameter of the SetProperty
methods. It is a callback delegate that is called when a property is changed. It also provides the old property value in its delegate parameter. So, you don’t need to cache the old property value:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set { SetProperty(value, onChanged: p => Debug.WriteLine("{0}: {1} -> {2}", p.PropertyName, p.OldValue, p.NewValue)); }
}
}
Adjusting Property Value
It is often required to adjust property value before it is set for fixing invalid input or other reasons. Adjusting property value is easy. For example, the following code shows a raw property setter that does not allow negative numbers:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set
{
if (_number == value)
return;
if (value < 0)
{
Debug.WriteLine("Number: {0} >> 0", value);
value = 0;
if (_number == value)
return;
}
_number = value;
OnPropertyChanged("Number");
}
}
}
If you use a SetProperty
method in the property setter, it will look like this:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set
{
if (Number == value)
return;
if (value < 0)
{
Debug.WriteLine("Number: {0} >> 0", value);
value = 0;
}
SetProperty(value);
}
}
}
The SetProperty
methods also provide the onChanging
parameter that is called before a property is changed. It enables you to override the new property value by its delegate parameter. So, you can use the onChanging
parameter like this:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set
{
SetProperty(value, onChanging: p =>
{
if (p.NewValue < 0)
{
Debug.WriteLine("{0}: {1} >> 0", p.PropertyName, p.NewValue);
p.NewValue = 0;
}
});
}
}
}
If you want to adjust property values in a centralized location, the PropertyValueChanging
event can be used like this:
public partial class Test : PropertyObservable
{
protected override void OnPropertyValueChanging(IPropertyValueChangingEventArgs e)
{
base.OnPropertyValueChanging(e);
if (e.PropertyName == "Number")
{
if (((int)e.NewValue != Number && (int)e.NewValue < 0)
{
Debug.WriteLine("Number: {0} >> 0", e.NewValue);
e.NewValue = 0;
}
return;
}
}
}
But be warned, like the PropertyValueChanged
event, the PropertyValueChanging
event is only raised when you set a property value by a SetProperty
method. Besides, the OldValue
and NewValue
are not type safe. And the PropertyValueChanging
event is raised before the onChaning
callback is called.
Supported Preprocessor Symbols
You can use the following preprocessor symbols:
- NICENIS_4C: Define this symbol if you want to compile with the .NET Framework 4 Client Profile.