【问题标题】:Raising PropertyChanged for a dependent property, when a prerequisite property is changed in another class?当另一个类中的先决属性更改时,为依赖属性提高 PropertyChanged?
【发布时间】:2017-09-25 00:15:33
【问题描述】:

我有这个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 属性中的任何一个发生更改时,都会为 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当另一个类中的先决属性发生更改时,如何为依赖属性引发 PropertyChanged

【问题讨论】:

    标签: c# wpf xaml data-binding inotifypropertychanged


    【解决方案1】:

    您可以通过多种不同的方式做到这一点。我见过许多不同的解决方案,包括自定义属性或在单个属性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.“检测到无限递归”的情况有简单的测试吗?
    • 广告 1):你不需要那个。如果 Account 实例需要比 Bank 类更有效,可以在 Bank 类中实现取消订阅,而无需修改 PropertyChangedPropagator 类。如果 Accounts 和 Bank 具有相同的生命周期,则事件取消订阅是无关紧要的。广告 2):这将是对 PropertyChangedPropagator 的测试,而不是对 Bank 类的测试。然而,它的测试非常简单(它当然存在)。只需将其连接起来以进行无限递归并检查它是否引发了预期的异常。
    • 如果我在 A 类中有属性 AnyState 怎么办。这基本上是从其他两个属性中得到的,每个属性都在一个单独的类中。道具 A 得到 { 返回 B.IsRunning || C.IsRunning;} B 类具有 IsRunning 属性,C 也是如此
    • @Luishg 如果我理解正确,这是完全相同的问题。 AnySateBank 类中的属性TotalB.IsRunningAccount1.BalanceC.IsRunning 在这个答案中是Account2.Balance
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    • 2017-04-24
    • 1970-01-01
    • 2021-04-02
    • 2017-08-05
    相关资源
    最近更新 更多