当先决条件 属性 在另一个 class 中发生更改时,为依赖项 属性 提高 PropertyChanged?
Raising PropertyChanged for a dependent property, when a prerequisite property is changed in another class?
我有这个 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
依赖于其他 classes 并且具有根据这些其他 classes 的属性计算的 属性 Total
。每当更改这些 Account.Balance
属性中的任何一个时,都会为 Account.Balance
引发 PropertyChanged
:
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));
}
}
我想在任何先决条件属性发生更改时为 Total
提出 PropertyChanged
。我怎样才能以一种易于测试的方式做到这一点?
TL;DR 当先决条件 属性 在另一个 class?
您可以通过多种不同的方式做到这一点。我见过许多不同的解决方案,它们涉及自定义属性或在单个 属性 setter
中引发多个 PropertyChanged
事件。我认为这些灵魂中的大多数都是反模式,并且不容易测试。
我和一位同事 (Robert Jørgensgaard Engdahl) 想到的最好的方法是静态 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;
}
};
}
}
它创建一个 PropertyChangedEventHandler
,您可以将其设置为侦听其他 classes 上的 PropertyChanged。它在抛出 WhosebugException
之前使用 InvalidOperationException
处理循环依赖。
要在上面的示例中使用静态 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));
}
我有这个 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
依赖于其他 classes 并且具有根据这些其他 classes 的属性计算的 属性 Total
。每当更改这些 Account.Balance
属性中的任何一个时,都会为 Account.Balance
引发 PropertyChanged
:
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));
}
}
我想在任何先决条件属性发生更改时为 Total
提出 PropertyChanged
。我怎样才能以一种易于测试的方式做到这一点?
TL;DR 当先决条件 属性 在另一个 class?
您可以通过多种不同的方式做到这一点。我见过许多不同的解决方案,它们涉及自定义属性或在单个 属性 setter
中引发多个 PropertyChanged
事件。我认为这些灵魂中的大多数都是反模式,并且不容易测试。
我和一位同事 (Robert Jørgensgaard Engdahl) 想到的最好的方法是静态 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;
}
};
}
}
它创建一个 PropertyChangedEventHandler
,您可以将其设置为侦听其他 classes 上的 PropertyChanged。它在抛出 WhosebugException
之前使用 InvalidOperationException
处理循环依赖。
要在上面的示例中使用静态 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));
}