August 12, 2021

Using CallMemberAttribute in an INotifyPropertyChanged Base Class

Here is an sample base clase that makes implementing INotifyPropertyChanged easy. It is also a nice example of using templated methods on a class in C# without making the whole class templated.
using System.Runtime.CompilerServices;
...

public abstract class NotifyProperyChangedBase : INotifyPropertyChanged
{
    /// <summary>
    /// Multicast event for property change notifications.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Checks if a property already matches a desired value.
    ///     Sets the property and
    ///     notifies listeners only when necessary.
    /// </summary>
    /// <typeparam name="T">Type of the property.</typeparam>
    /// <param name="storage">Reference to a property with 
    /// both getter and setter.</param>
    /// <param name="value">Desired value for the property.</param>
    /// <param name="propertyName">
    ///     Name of the property used to notify listeners.  This
    ///     value is optional and can be provided automatically 
    ///     when invoked from compilers that support CallerMemberName.
    /// </param>
    /// <returns>
    ///     True if the value was changed, false if the existing
    ///     value matched the desired value.
    /// </returns>
    protected bool SetProperty<T>(
        ref T storage, 
        T value, 
        [CallerMemberName] String propertyName = null)
    {
        bool valueChanged = !EqualityComparer<T>.Default.Equals(storage, value);
        if (valueChanged)
        {
            storage = value;
            OnPropertyChanged(propertyName);
            valueChanged = true;
        }
        return valueChanged;
    }

    /// <summary>
    ///     Checks if a property already matches a desired value.
    ///     Sets the property and notifies listeners only when 
    ///     necessary.
    /// </summary>
    /// <typeparam name="T">Type of the property.</typeparam>
    /// <param name="storage">Reference to a property 
    /// with both getter and setter.</param>
    /// <param name="value">Desired value for the property.</param>
    /// <param name="equalityComparer">
    ///     Specify the equality comparer.
    ///     For example if it is a non-case sensitive string you could use
    ///     StringComparer.CurrentCultureIgnoreCase</param>
    /// <param name="propertyName">
    ///     Name of the property used to notify listeners.  This
    ///     value is optional and can be provided automatically
    ///     when invoked from compilers that support CallerMemberName.
    /// </param>
    /// <returns>
    ///     True if the value was changed, false if the existing
    ///     value matched the desired value.
    /// </returns>
    protected bool SetProperty<T>(
        ref T storage, 
        T value, 
        IEqualityComparer<T> equalityComparer,
        [CallerMemberName] String propertyName = null)
    {
        bool valueChanged = !equalityComparer.Equals(storage, value);

        if (valueChanged)
        {
            storage = value;
            OnPropertyChanged(propertyName);
            valueChanged = true;
        }

        return valueChanged;
    }

    /// <summary>
    ///     Notifies listeners that a property value has changed.
    /// </summary>
    /// <param name="propertyName">
    ///     Name of the property used to notify listeners. This
    ///     value is optional and can be provided automatically
    ///     when invoked from compilers that support 
    ///     <see cref="CallerMemberNameAttribute" />.
    /// </param>
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
and here is an example usage inside a ViewModel class:
private string _notifyIconToolTipText = "";

public string NotifyIconToolTipText
{
    get
    {
        return _notifyIconToolTipText;               
    }
    set
    {
        string newValue = value ?? "";
        this.SetProperty(ref _notifyIconToolTipText, newValue);
    }
}

No comments: