DEV Community

Multi
Multi

Posted on • Updated on

I Changed INotifyPropertyChanged

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
public class Foo : Container
{
    public int Bar 
    {
        get => (int) this["Bar"];
        set => this["Bar"] = value;
    }
}
Enter fullscreen mode Exit fullscreen mode

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 a KeyNotFoundException)
  • 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));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
public class MyViewModel : PropertyChangedHelper
{
    public bool MyProperty
    {
        get => GetProperty<int>();
        set => SetProperty<int>(value);
    }
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
davidsackstein profile image
david-sackstein

Clean, simple and well explained.
I will definitely use this in my next WPF project.