Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
379 views
in Technique[技术] by (71.8m points)

c# - Raising PropertyChanged for a dependent property, when a prerequisite property is changed in another class?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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));
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...