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));
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…