Raising PropertyChanged for a dependent property, when a prerequisite property is changed in another class?

江枫思渺然 提交于 2019-12-17 19:03:37

问题


I have this Bank class:

public class Bank : INotifyPropertyChanged
{
    public Bank(Account account1, Account account2)
    {
        Account1 = account1;
        Account2 = account2;
    }

    public Account Account1 { get; }
    public Account Account2 { get; }

    public int Total => Account1.Balance + Account2.Balance;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Bank depends on other classes and has a property Total that is calculated from properties of these other classes. Whenever any of these Account.Balance properties is changed, PropertyChanged is raised for Account.Balance:

public class Account : INotifyPropertyChanged
{
    private int _balance;

    public int Balance
    {
        get { return _balance; }
        set
        {
            _balance = value;
            RaisePropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

I would like to raise PropertyChanged for Total, whenever any of the prerequisite properties is changed. How can I do this in a way that is easily testable?

TL;DR How do I raise PropertyChanged for a dependent property, when a prerequisite property is changed in another class?


回答1:


You can do this in many different ways. I have seen many different solutions, that invovle custom attributes or raising multiple PropertyChanged events in a single property setter. I think most of these soultions are anti-patterns, and not easily testable.

The best way a colleague (Robert Jørgensgaard Engdahl) and I have come up with is this static class:

public static class PropertyChangedPropagator
{
    public static PropertyChangedEventHandler Create(string sourcePropertyName, string dependantPropertyName, Action<string> raisePropertyChanged)
    {
        var infiniteRecursionDetected = false;
        return (sender, args) =>
        {
            try
            {
                if (args.PropertyName != sourcePropertyName) return;
                if (infiniteRecursionDetected)
                {
                    throw new InvalidOperationException("Infinite recursion detected");
                }
                infiniteRecursionDetected = true;
                raisePropertyChanged(dependantPropertyName);
            }
            finally
            {
                infiniteRecursionDetected = false;
            }
        };
    }
}

It creates a PropertyChangedEventHandler, which you can set up to listen on PropertyChanged on other classes. It handles circular dependencies with an InvalidOperationException before an StackOverflowException is thrown.

To use the static PropertyChangedPropagator in the example from above, you will have to add one line of code for each prerequisite property:

public class Bank : INotifyPropertyChanged
{
    public Bank(Account account1, Account account2)
    {
        Account1 = account1;
        Account2 = account2;
        Account1.PropertyChanged += PropertyChangedPropagator.Create(nameof(Account.Balance), nameof(Total), RaisePropertyChanged);
        Account2.PropertyChanged += PropertyChangedPropagator.Create(nameof(Account.Balance), nameof(Total), RaisePropertyChanged);
    }

    public Account Account1 { get; }
    public Account Account2 { get; }

    public int Total => Account1.Balance + Account2.Balance;


    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

This is easily testable (pseudo code):

[Test]
public void Total_PropertyChanged_Is_Raised_When_Account1_Balance_Is_Changed()
{
    var bank = new Bank(new Account(), new Account());

    bank.Account1.Balance += 10;

    Assert.PropertyChanged(bank, nameof(Bank.Total));
}


来源:https://stackoverflow.com/questions/43653750/raising-propertychanged-for-a-dependent-property-when-a-prerequisite-property-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!