I would like to link up my post from yesterday, in which I described how to implement a ViewModel with INotifyPropertyChanged interface in WPF context in a compiler-safe manner.

.NET in combination with C# offers a very elegant solution with custom attributes to link properties, for example.

Today I will show how to implement a custom attribute with reflection, which ensures that a property marked with this attribute triggers a PropertyChanged event when the value of another property changes.

In this context, I would like to refer again to my GitHub repository from last time.

Attribute class

With an attribute you are able to store constant meta information for a .NET entity, like a class or a method, at compiler time.

What we want is to store the information for a property which listens for the changes of another property, that triggers PropertyChanged event.

For this we need the name of the source property as information:

using System;

// ...

[AttributeUsage(AttributeTargets.Property,
                AllowMultiple = true,
                Inherited = false)]
public class ReceiveNotificationFromAttribute : Attribute
{
    #region Constructors (1)

    /// <summary>
    /// Initializes a new instance of the <see cref="ReceiveNotificationFromAttribute" /> class.
    /// </summary>
    /// <param name="senderName">
    /// The value for the <see cref="ReceiveNotificationFromAttribute.SenderName" /> property.
    /// </param>
    public ReceiveNotificationFromAttribute(string senderName)
    {
        SenderName = senderName;
    }

    #endregion Constructors

    #region Properties (1)

    /// <summary>
    /// Gets or sets the name of sender / sending member of the notification.
    /// </summary>
    public string SenderName
    {
        get;
        set;
    }

    #endregion Properties
}

Later you can use the attribute the following way:

// ...

class MyViewModel : INotifyPropertyChanged
{
    // ...

    public string? Name
    {
        get { return this.Get(); }
        
        set { this.Set(value); }
    }

    [ReceiveNotificationFrom("Name")]
    public string? UpperName
    {
        get
        {
            if (this.Name == null)
            {
                return null;
            }
        
            return this.Name.ToUpper();
        }
    }

    // ...
}

Our goal: Trigger PropertyChanged event for UpperName everytime Name changes.

In the ViewModel we can start with adding helper methods RaisePropertyChanged and HandleReceiveNotificationFromAttributes:

// ...

class MyViewModel : INotifyPropertyChanged
{
    // ...

    protected void Set<TProperty>(TProperty newValue, [CallerMemberName] string propertyName = "")
    {
        _properties[propertyName] = newValue;
        
        // first raise notification for "this" property
        // named in `propertyName`
        RaisePropertyChanged(propertyName);
        // now for all that uses `ReceiveNotificationFrom`
        // and are linked with "this"
        HandleReceiveNotificationFromAttributes(propertyName);
    }

    protected RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected void HandleReceiveNotificationFromAttributes(string propertyName)
    {
        // ..
    }

    // ...
}

With reflection we start to detect all properties in the MyViewModel class:

// ...

    protected void HandleReceiveNotificationFromAttributes(string propertyName)
    {
        // get all public and non-public instance properties
        var allProperties = GetType()
            .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        // ...
    }

// ...
}

Next we filter all properties which using / containing ReceiveNotificationFrom attributes:

// ...
using System.Linq;

// ...

    protected void HandleReceiveNotificationFromAttributes(string propertyName)
    {
        // ...
        var allPropertiesWithReceiveNotificationFromAttributes =
            allProperties.Select((property) =>
            {
                // we collect attributes
                // and property together
                // in a new anonymous object
                return new
                {
                    Attributes = property.GetCustomAttributes(typeof(ReceiveNotificationFromAttribute), false)
                                         .Cast<ReceiveNotificationFromAttribute>()
                                         .ToArray(),

                    Property = property,
                };
            })
            .Where((x) =>
            {
                return x.Count > 0;
            })
            .ToArray();

        // ...
    }

// ...
}

Now we filter only the property with ReceiveNotificationFrom that have the same value in their Name property as the current value in propertyName:

// ...

    protected void HandleReceiveNotificationFromAttributes(string propertyName)
    {
        // ...
        var allMatchingProperties =
            allPropertiesWithReceiveNotificationFromAttributes.Where((x) =>
            {
                // ensure to have only 1 or 0
                // attributes, otherwise `SingleOrDefault()`
                // will throw an error
                var attrib = x.Attributes.SingleOrDefault(y => y.SenderName == propertyName);
                
                return attrib != null;
            })
            .ToArray();

        // ...
    }

// ...
}

Finally, after we got all required information, we can start sending the notifications:

// ...

    protected void HandleReceiveNotificationFromAttributes(string propertyName)
    {
        // ...
        
        // also ensure not to have duplicates
        var propertiesToNotifyByName =
            allMatchingProperties.Select(x => x.Property.Name)
                                 .Distinct(StringComparer.Ordinal)
                                 .ToArray();

        foreach (var propertyName in propertiesToNotifyByName)
        {
            RaisePropertyChanged(propertyName);
        }
    }

// ...
}

Conclusion

Reflection is a very powerful tool for runtime operation. Especially in the WPF, WCF and ASP.NET contextes it is often used.

Keep in mind that reflection is relative slow and may produce runtime errors, because with it you usually leave the world of strong typing of C# & Co.

Have fun while trying it out! 🎉