在另一个类中更改先决条件属性时,为依赖属性引发PropertyChanged?

时间:2017-04-27 09:23:05

标签: c# wpf xaml data-binding inotifypropertychanged

我有这个Bank课程:

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依赖于其他类,并且具有根据这些其他类的属性计算的属性Total。每当更改Account.Balance个属性中的任何一个时,PropertyChanged都会引发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));
    }
}

每当更改任何先决条件属性时,我想为PropertyChanged引发Total。我怎样才能以易于测试的方式做到这一点?

TL; DR 当在另一个类中更改先决条件属性时,如何为依赖属性引发PropertyChanged

1 个答案:

答案 0 :(得分:5)

您可以通过多种不同方式完成此操作。我已经看到了许多不同的解决方案,这些解决方案可以在单个属性PropertyChanged中调用自定义属性或引发多个setter事件。我认为这些灵魂大部分都是反模式,并且不易测试。

同事(Robert Jørgensgaard Engdahl)和我提出的最佳方式是这个静态类:

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;
            }
        };
    }
}

它会创建一个PropertyChangedEventHandler,您可以将其设置为在其他类上侦听PropertyChanged。它在抛出InvalidOperationException之前处理与StackOverflowException的循环依赖关系。

要使用上面示例中的静态PropertyChangedPropagator,您必须为每个先决条件属性添加一行代码:

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));
    }
}

这很容易测试(伪代码):

[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));
}