我有这个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
?
答案 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));
}