The original approach to INotifyPropertyChanged.
Hi all, it has always bothered me that one has to type so much code in order to implement property binding notification for every property.
public class MyViewModel : INotifyPropertyChanged
{
private int _myProperty;
public int MyProperty
{
get => _myProperty;
set
{
_myProperty = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
A simpler approach to INotifyPropertyChanged
However, I got an idea from this creative Stack Overflow answer:
public abstract class Container : INotifyPropertyChanged
{
Dictionary<string, object> values;
public event PropertyChangedEventHandler PropertyChanged;
protected object this[string name]
{
get => values[name];
set
{
values[name] = value;
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
public class Foo : Container
{
public int Bar
{
get => (int) this["Bar"];
set => this["Bar"] = value;
}
}
I decided to use a similar approach because:
- The solution above did not handle a case where there is no such element in the
values
dictionary (which results in aKeyNotFoundException
) - I wanted to avoid passing the name of the property (the solution is the
CallerMemberName
attribute, that is being used in the original approach) - I preferred to use generic methods rather than indexers (
["name"]
).
public class PropertyChangedHelper : INotifyPropertyChanged
{
private readonly Dictionary<string, object> _values = new Dictionary<string, object>();
public event PropertyChangedEventHandler PropertyChanged;
public T GetProperty<T>([CallerMemberName] string propertyName = "")
{
EnsureElement<T>(propertyName);
return (T) _values[propertyName];
}
public void SetProperty<T>(object value, [CallerMemberName] string propertyName = "")
{
EnsureElement<T>(propertyName);
if (_values[propertyName] == value)
{
return;
}
_values[propertyName] = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void EnsureElement<T>(string propertyName)
{
if (!_values.ContainsKey(propertyName))
{
_values.Add(propertyName, default(T));
}
}
}
public class MyViewModel : PropertyChangedHelper
{
public bool MyProperty
{
get => GetProperty<int>();
set => SetProperty<int>(value);
}
}
Performance
Thanks a lot for benchmarking this solution for me!
Some of you may be skeptic about the approach above, so a friend of mine - jeuxjeux20
has benchmarked (Using BenchmarkDotNet) the original, longer approach to property binding notification, and the new, hassle-free approach:
Method | Mean (Time) | Error (Margin) | Standard Deviation |
---|---|---|---|
GetPropertyValue (Original Approach) | 46.3 ns | 0.0818 ns | 0.0765 ns |
SetPropertyValue (Original Approach) | 238.16 ns | 1.8122 ns | 1.6951 ns |
GetPropertyValue (New Approach) | 213.09 ns | 2.8977 ns | 2.7105 ns |
SetPropertyValue (New Approach) | 539.47 ns | 3.7321 ns | 3.4910 ns |
- ns = nanosecond (1/1,000,000,000 of a second)
The code that jeuxjeux20
wrote in order to test the approach:
Final Words
Thanks for reading this post!
I hope it has helped you creating a more elegant solution for property binding notification for your properties!
- Multi!
Top comments (1)
Clean, simple and well explained.
I will definitely use this in my next WPF project.