Home > Software design >  How to write less boilerplate for property changed notifications?
How to write less boilerplate for property changed notifications?

Time:09-28

I've got a MVVM-ish application which ended up with a model with way too many property change notifications. Specifically, I'm sometimes missing some notifications because there's too many of them.

For example I end up with properties like this:

public string CustomEmail {
    get => customEmail;
    set
    {
        customEmail = value;
        OnChanged("CustomEmail");
        OnChanged("IsSendAllowed");
        OnChanged("IsNotFaxEmail");
    }
}

Is there a better way to organise it? For example is there a way to mark a property [DependsOn("CustomEmail")] bool IsNotFaxEmail { ... }?

Or if most of the properties are used for bindings, should I be going all in on converters instead? I'd rather not end up with a silly number of converters like {Binding CustomEmail, Converter=EmailIsFaxToElementVisibilityConverter}.

Am I missing some simpler solution?

CodePudding user response:

You may write a NotifyPropertyChanged method that accepts multiple property names. It does not really reduce the amount of code, but at least allows to make only one method call.

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(params string[] propertyNames)
    {
        var propertyChanged = PropertyChanged;

        if (propertyChanged != null)
        {
            foreach (var propertyName in propertyNames)
            {
                propertyChanged.Invoke(this,
                   new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

public class ViewModel : ViewModelBase
{
    private string customEmail;

    public string CustomEmail
    {
        get => customEmail;
        set
        {
            customEmail = value;

            NotifyPropertyChanged(
                nameof(CustomEmail),
                nameof(IsSendAllowed),
                nameof(IsNotFaxEmail));
        }
    }
}

CodePudding user response:

I don't often find so many dependencies but I can outline a solution I've seen. Create an attribute. Call it AlsoRaise attribute which takes a string parameter. You can probably think of a better name. But I think dependson isn't quite right because it's the other way round.

[AlsoRaise(nameof(IsSendAllowed))]
[AlsoRaise(nameof(IsNotFaxEmail))]
public string CustomEmail

You then have something can drive your list of other properties you're going to raise change notification for as well as CustomEmail.

In a static constructor you fill a dictionary<string, string[]> using those attributes. You iterate public properties, grab those attributes.

In OnChanged you look up your property name in that dictionary and raise property changed for the property name plus any strings you find. Or none of course.

I may have forgotten some part, while since I saw this implementation.

CodePudding user response:

I think PropertyChanged.Fody is exactly what you are looking for. It is a specialized library for Fody, which is a code weaving tool. It enables manipulating the IL code of an assembly on build e.g. for adding boilerplate code to classes like implementing property changed notifications.

PropertyChanged.Fody is very powerful in that it can automatically implement INotifyPropertyChanged. Below is an example from the official repository. It is all you have to write, Fody will add (weave in) the rest.

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
    public string FullName => $"{GivenNames} {FamilyName}";
}

Furthermore, dependent properties like GivenNames and FamilyName in this example are detected and will notify a property change for FullName if they are changed, too. There are lots of options to configure property changed notifications manually through attributes like:

These attributes are the most useful for your scenario. Your sample would be reduced to:

[AlsoNotifyFor("IsSendAllowed")]
[AlsoNotifyFor("IsNotFaxEmail")]
public string CustomEmail { get; set; }

Or the other way around with DependsOn:

[DependsOn("CustomEmail")]
public string IsSendAllowed { get; set; }

[DependsOn("CustomEmail")]
public string IsNotFaxEmail { get; set; }

For more examples and an overview of all attributes and other powerful mechanisms of Fody, have a look at the GitHub Wiki.

  • Related