DEV Community

Multi
Multi

Posted on • Edited on

5 3

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:

[Benchmark]
public static void SetPropertiesOnClassic()
{
var myClassic = new ClassicClass();
myClassic.Meme = 42;
myClassic.Name = "Memetic";
}
[Benchmark]
public static void GetPropertiesOnClassic()
{
var myClassic = new ClassicClass();
var thing1 = myClassic.Meme;
var thing2 = myClassic.Name;
}
[Benchmark]
public static void SetPropertiesOnCustom()
{
var custom = new CustomClass();
custom.Meme = 42;
custom.Name = "Memetic";
}
[Benchmark]
public static void GetPropertiesOnCustom()
{
var custom = new CustomClass();
var thing1 = custom.Meme;
var thing2 = custom.Name;
}
public class ClassicClass : INotifyPropertyChanged
{
private int _meme = 0;
public int Meme
{
get { return _meme; }
set
{
_meme = value;
OnPropertyChanged();
}
}
private string _name = "Hello";
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
}
public class CustomClass : PropertyChangedHelper
{
public int Meme
{
get => GetProperty(0);
set => SetProperty(value);
}
public string Name
{
get => GetProperty("Name");
set => SetProperty(value);
}
}

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.

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →