Home > Enterprise >  Don't Understand How INotifyPropertyChanged function
Don't Understand How INotifyPropertyChanged function

Time:12-13

INotifyPropertyChanged interface implementation (appropriate event) and helper method works fine in my code but I really don't understand how it works. My book does a poor job of explaining it to me in my opinion or I am not very smart. We have a separate class Car.cs added to my solution with the following code (example from the book) and it should work in TwoWay Binding regards to TextBox control in my WPF application being changed when object's instance is being changed too:

public class Car: INotifyPropertyChanged
{
private string _make;
private string _model;

public event PropertyChangedEventHandler PropertyChanged;

public Car()
{

}
    
public string Make
{
    get { return _make; }
    set
    {
        if (_make != value)
        {
            _make = value;
            OnPropertyChanged("Make");
        }
    }
}
public string Model
{
    get { return _model; }
    set
    {
        if(_model != value)
        {
            _model = value;
            OnPropertyChanged("Model");
        }
    }
}

private void OnPropertyChanged (string propertyName)
{
    if (PropertyChanged != null)
    {
        
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}}

And here is the code which I made myself after learning Microsoft learning materials and it looks and works better imo:

public class Car: INotifyPropertyChanged
{
private string _make; 
private string _model;

public event PropertyChangedEventHandler PropertyChanged;

public Car()
{

}
    
public string Make
{
    get { return _make; }
    set
    { 
        _make = value;
        OnPropertyChanged(); 
    }
}
public string Model
{
    get { return _model; }
    set
    {
        _model = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged ()
{
    if (PropertyChanged != null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(""));
    }
} }

So I have the foolowing question regards to example from the book (the first one):

  1. What this part means? What's the point to check if property not equals to value (never saw anything like it before when during the Set part of the property you check that field of the class checked for being not equalled to value)? Code works even without it. No explanation for it in the book. Here is the code under question:

    set { if (_make != value) { _make = value; OnPropertyChanged("Make"); }

  2. Why if you'll write the name of the property without brackets in the above-mentioned small piece of code (in subparagraph one) then it will not work? I meant if you'll write Make then it does not work and you should write "Make".

Thanks in advance!

CodePudding user response:

INotifyPropertyChanged is an interface which only requires a class to implement it's requirements. Which is basically "just" an Event.

public interface INotifyPropertyChanged
{
    //
    // Summary:
    //     Occurs when a property value changes.
    event PropertyChangedEventHandler? PropertyChanged;
}

The Method OnPropertyChanged is just a "helper" to raise this event. But the important part here, is that the INotifyPropertyChanged implementation by itself is not doing anything.

However WinForms and WPF are automatically subscribing in some higher-hirachy classes to such Event. They identify such classes through reflection and add themself to one of the subscribers to the PropertyChanged Event - which your class still needs to raise.

For this purpose you need to override the Setters of your properties:

public string Examle
{
    get { return _field; }
    set
    { 
        _field = value;
        OnPropertyChanged(); // <---
    }
}

Otherwise the subscriber will never be notified. However the whole purpose of this is to decouple the handling of changes from the "Model" so your class which act's as container to the data - since the container can be passed to different forms, which might want to handle this container different.

The check if the value actually changed is only something you CAN do - to prevent the notification of the subscriber (let's say your data grid) to re-run all validations you've setup for such property. To avoid unnecessary runs of such validations this if will prevent notifying subscribers of such changes where a value get's re-assigned, but it remains the same.

The auto-wiring of the subscriber however is abstraced for you as developer, which can create difficulties for beginners to understand - however makes your life much more easier on the long run, as you can rely on the Framework as such (less for you to implement, test, etc.) so it's just a "convention" to use INotifyPropertyChange Interface, but the basic building blocks are general C# constructs.

Hope this helped a little :)

CodePudding user response:

Here is a basic example of how to correctly implement the IPropertyChanged interface, which notifies the view to re-draw on property changing its value.

public class PropertyChangedEventStackOverflow : INotifyPropertyChanged
{
    //We have a private property of the same public property. This allows us to store the privous value of the property which we can 
    //get at any time when we ask.
    string name = string.Empty;
    //Public property is required to do two-way-binding or one-way-binding from model to the view.
    public string Name
    {
        get => name;
        set
        {
            if (name == value) //Check if the private property is already equals to the exact same value to avoid extra memory 
                return;        //and uneccasary screen re-drawing. So we just return and avoid calling the OnPropertyChanged.
            name = value;      //If the value is different meaning we want to notify view and tell it to re-draw the screen for us.
            OnPropertyChanged(nameof(Name));
        }
    }

    //We create this helper metod to avoid doing something like this every single time we want to notify
    string surname = string.Empty;
    public string Surname
    {
        get => surname;
        set
        {
            if (surname == value)
                return;
            surname = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Surname)));
        }
    }

    //Here is the method to create for OnPropertyChangedEvent
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

If you want to go step further and want to have an Action when your property has changed you can do something like this. backingStore type T = private property

public bool SetProperty<T>(ref T backingStore, T value,
    [CallerMemberName] string propertyName = "",
    Action onChanged = null)
{
    if (EqualityComparer<T>.Default.Equals(backingStore, value))
        return false;

    backingStore = value;
    onChanged?.Invoke();
    OnPropertyChanged(propertyName);
    return true;
}

You can give it an action to execute if once a property has changed.

CodePudding user response:

  1. Because raising the PropertyChanged event can be expensive in terms of performance (for example when a complex view has to redraw itself in order to display the latest data), it's a recommended pattern to raise it only when necessary.

    It's only necessary when the new value has changed. To update the binding when the new value and the old value are the same is redundant and potentially expensive.

    The following example is a very verbose version to highlight what's going on:

private string myProperty;
public string MyProperty
{
  get => this.myProperty;
  set
  {
    string newValue = value;
    string oldValue = this.MyProperty;
    if (newValue != oldValue)
    {
      this.myProperty = newValue;
      OnPropertyChanged(nameof(this.MyProperty));
    }
  }
}

For reasons of performance, passing an empty string (string.Empty or "") or null to the PropertyChangedEventArgs instance, instead of the property name, will instruct the binding engine to update all bindings of the class that raises the PropertyChanged event.
In other words, it's like raising the PropertyChanged event for all properties at once.
That's why you only do this when this behavior is explicitly desired (for example when you reset and clear all properties):

/* Will raise the PropertyChanged event for ALL properties defined by the declaring type */

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(""));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));

/* Will raise the PropertyChanged event ONLY for the particular property that matches the provided property name */

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyProperty"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.MyProperty)));
  1. "Make" is called a string literal. Every literal enclosed in double-quotes is interpreted as plain text by the compiler (string literal). If it wasn't the double-quotes, the compiler expects the value to be a language object (for example a variable or type).
    If you check the signature of the constructor of PropertyChangedEventArgs you will see that it expects the property name as string.

    Using Make (without the double-quotes) is a simple value reference (local or member variable) that returns the value of the Make property and not the name of the property (member name).
    The property name is required to tell the binding engine which property has changed and therefore which Binding needs to be updated.

    The following example assumes that the property Make is set to a value of "BMW":

public string Make
{
  get => this.make; 
  set
  { 
    // For example 'value' is "BMW"
    this.make = value;

    // Invocation a)  
    // Pass the property name as argument.    
    OnPropertyChanged("Make"); // Same as: OnPropertyChanged(nameof(this.Make))

    // Invocation b)
    // Pass the property value as argument
    OnPropertyChanged(Make); 

    // Verbose version of b)
    string modelMake = Make;
    OnPropertyChanged(modelMake);     
  }
}

Version b) won't work, because the binding engine needs the property's member name and not the property value.

See Microsoft Docs: Strings and string literals to learn about strings.

See INotifyPropertyChanged to learn the recommended pattern to implement the interface

Remarks

Checking variable (for example an event delegate) for null using an if statement and the Null-conditional operator ?. or ?[] is redundant.
In high performance code you would want to avoid the redundant double checking.

The following versions are the same:

private void OnPropertyChanged()
{
  if (PropertyChanged != null)
  {
    PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
} 

private void OnPropertyChanged()
{
  PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
} 

private void OnPropertyChanged(string propertyName) => PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));

CodePudding user response:

What's the point to check if property not equals to value

it is just to avoid unnecessary updates. It is not strictly required. Whenever you have events to signal that something is updated it is often a good idea to check if the thing actually changed, since you do not know what is listening to the event, it could potentially trigger some slow computations.

Why if you'll write the name of the property without brackets in the above-mentioned small piece of code (in subparagraph one) then it will not work?

Because the event needs a string. You can let the compiler insert this string for you by writing OnPropertyChanged(nameof(Make)); You can also use CallerMemberName attribute to let the compiler insert this automatically, just call OnPropertyChanged() within your property.

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Going one step further you you can create a method that does both the comparison, field update, and event, letting you write a one line setter => Set(ref _make, value);

private void Set<T>(ref T field, T value, [CallerMemberName] string caller = "")
{
    if (!EqualityComparer<T>.Default.Equals(field, value))
    {
        field = value;
        OnPropertyChanged(caller);
    }
}

However, my preference tend to be to wrap all of this inside a separate class:

    public class Changeable<T> : INotifyPropertyChanged
    {
        private T currentValue;
        public Changeable(T initialValue) => currentValue = initialValue;
        public T Value
        {
            get => currentValue;
            set
            {
                if (!EqualityComparer<T>.Default.Equals(currentValue, value))
                {
                    this.currentValue = value;
                    OnPropertyChanged();
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

That way you can declare a property like public Changeable<string> Make {get;} = new (""); and bind to it like {Binding Make.Value}

  • Related