当另一个类的先决属性发生更改时,为依赖属性引发PropertyChanged事件?

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

当任何一个前提属性发生更改时,我希望可以为Total引发PropertyChanged。我该如何以一种易于测试的方式实现这一点?

简而言之:当先决条件属性在另一个类中更改时,如何为一个依赖属性引发PropertyChanged

1个回答

6
你可以用很多不同的方式来做到这一点。我见过很多不同的解决方案,其中包括自定义属性或在单个属性setter中引发多个PropertyChanged事件。我认为大多数这些解决方案都是反模式,并且不容易测试。
我和同事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。它通过在抛出 StackOverflowException 之前抛出 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));
}

  1. PropertyChangedPropagator没有一种方法来处理INPC订阅的释放。
  2. 是否有一种简单的测试方法来检测“检测到无限递归”情况?
- ASh
2
广告1):你不需要那个。如果账户实例需要比银行类更长的生命周期,可以在银行类中实现取消订阅而不修改PropertyChangedPropagator类。如果账户和银行具有相同的生命周期,则事件取消订阅无关紧要。广告2):那将是对PropertyChangedPropagator而不是Bank类的测试。然而,它非常简单易行(当然存在)。只需将其连接到无限递归并检查它是否抛出了预期的异常即可。 - Robert Jørgensgaard Engdahl
如果在A类中有一个名为AnyState的属性,它基本上是从另外两个属性获取的,每个属性都在不同的类中。prop A get { return B.IsRunning || C.IsRunning;} 类B有一个名为IsRunning的属性,C也有。 - Luishg
1
如果我理解正确的话,这就是完全相同的问题。在这个答案中,AnyStateBank类中的属性TotalB.IsRunningAccount1.Balance,而C.IsRunningAccount2.Balance。@Luishg - Jogge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接