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?
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));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With