Max
Max

Reputation: 20004

How to avoid implementing INotifyPropertyChanged manually

Is there some way to avoid this. I have a lot of classes that are bound to DataGridViews and they are just simple collection of properties with default getter and setter. So these classes are very simple. Now I need to implement INotifyPropertyChanged interface for them which will increase the amount of code a lot. Is there any class that I can inherit from to avoid writing all this boring code? I image that I can inherit my classes from some class and decorate the properties with some attributes and it will do the magic. Is that possible?

I'm well aware of Aspect Oriented Programming, but I'd rather do it object oriented way.

Upvotes: 8

Views: 2033

Answers (8)

bh_earth0
bh_earth0

Reputation: 2818

improvements on leepie.

  • this handles a case where there is no such element in the values dictionary (which results in a KeyNotFoundException)
  • avoid passing the name of the property ( using CallerMemberName attribute )
  • use generic methods rather than indexers (["name"]).

usage:

public class MyViewModel : PropertyChangedHelper
{
    public int Bar
    {
        get => GetProperty<int>();
        set => SetProperty<int>(value);
    }
}

code:

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));
        }
    }
}

from : devto there is also benchmark in there.

Upvotes: 0

Anton
Anton

Reputation: 7719

Here is a similar solution to Marc, that has been extended to allow multiple property onpropertychanges and multiple RaiseCanExecuteChanged

simplest example usage

string _firstName;
public string FirstName
{
    get { return _firstName; }
    set { OnPropertyChanged(ref _firstName, value, "FirstName"); }
}

advanced example using multiple property updates and multiple commands

string _firstName;
public string FirstName
{
    get { return _firstName; }
    set { OnPropertyChanged(ref _firstName, value, "FirstName", "FullName", Command1, Command2); }
}

The advanced example calls OnProperty changed on firstname and fullname and also calls RaiseCanExecuteChanged for command1 and command2

base ViewModel code

protected void OnPropertyChanged<T>(ref T field, T value, params object[] updateThese)
{
    if (!EqualityComparer<T>.Default.Equals(field, value))
    {
        field = value;
        OnPropertyChanged(updateThese);
    }
}

protected void OnPropertyChanged(params object[] updateThese)
{
    if (PropertyChanged != null)
    {
        foreach (string property in updateThese.Where(property => property is string))
            PropertyChanged(this, new PropertyChangedEventArgs(property));

        foreach (DelegateCommand<object> command in updateThese.Where(property => property is DelegateCommand<object>))
            command.RaiseCanExecuteChanged();
    }
}

Upvotes: 1

Ian Ringrose
Ian Ringrose

Reputation: 51897

I have just found ActiveSharp - Automatic INotifyPropertyChanged, I have yet to use it, but it looks good.

To quote from it's web site...


Send property change notifications without specifying property name as a string.

Instead, write properties like this:

public int Foo
{
    get { return _foo; }
    set { SetValue(ref _foo, value); }  // <-- no property name here
}

Note that there is no need to include the name of the property as a string. ActiveSharp reliably and correctly figures that out for itself. It works based on the fact that your property implementation passes the backing field (_foo) by ref. (ActiveSharp uses that "by ref" call to identify which backing field was passed, and from the field it identifies the property).

Upvotes: 0

Dr Herbie
Dr Herbie

Reputation: 3940

Without AOP, I don't think there is an easy way to retrofit this to your existing classes. However you do it, you're at the very least going to have to change all your properties.

I use a base class inheriting INotifyPropertyChanged with an OnPropertyChanged(string propertyName) method to fire the event. I then use a Visual Studio Code snippet to create properties that automatically call OnPropertyChanged in the property setter.

Upvotes: 1

codekaizen
codekaizen

Reputation: 27419

Using code gen (say, T4) is another way. Check the discussion at: Automatic INotifyPropertyChanged Implementation through T4 code generation?.

I use this method, and it works well.

Upvotes: 0

leppie
leppie

Reputation: 117220

Create a container base class, eg:

abstract class Container : INotifyPropertyChanged
{
  Dictionary<string, object> values;

  protected object this[string name]
  {
    get {return values[name]; }
    set 
    { 
      values[name] = value;
      PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
  }
}

class Foo : Container
{
  public int Bar 
  {
    {get {return (int) this["Bar"]; }}
    {set { this["Bar"] = value; } }
  }
}

Note: very simplified code

Upvotes: 5

Marc Gravell
Marc Gravell

Reputation: 1062725

It depends; you could use PostSharp to write such an attribute that is re-written by the weaver; however, I would be tempted to just do it manually - perhaps using a common method for handling the data updates, i.e.

private string name;
public string Name {
    get { return name; }
    set { Notify.SetField(ref name, value, PropertyChanged, this, "Name"); }
}

with:

public static class Notify {
    public static bool SetField<T>(ref T field, T value,
         PropertyChangedEventHandler handler, object sender, string propertyName)
    {
        if(!EqualityComparer<T>.Default.Equals(field,value)) {
            field = value;
            if(handler!=null) {
                handler(sender, new PropertyChangedEventArgs(propertyName));
            }
            return true;
        }
        return false;
    }
}

Upvotes: 9

Jon Skeet
Jon Skeet

Reputation: 1500275

If you're amenable to AOP, you could try using PostSharp. Search for PostSharp INotifyPropertyChanged and you'll find lots of articles explaining it, such as this and this.

Upvotes: 0

Related Questions