0

I've got some classes here that all more or less rely on each other. The relationships form kinda like a dependency tree:

class A {
  List<B> _bs = new List<B>();

  public int ValueOfA { 
    get {
      return _bs.Sum(p => p.ValueOfB);
    }
}

class B {
  List<C> _cs = new List<C>();
  public int ValueOfB {
    get {
      return _cs.Where(p => p.ValueOfC > 1).Sum(p => p.ValuOfC);
    }
  }

class C {
  public int ValueOfC { get; set }
}

So, whenever _bs, _cs or ValueOfC change, every property relating to them should be notified as has changed, too, and hence be recalculated.

What's the best way of consistently and reliably achieving this goal? Is there by any chance a way to do this automatically?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
Hendrik Wiese
  • 2,010
  • 3
  • 22
  • 49

1 Answers1

0

You'll want to implement INotifyPropertyChanged with your class C. In the set of ValueOfC, you'll fire the event:

class C : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int valueOfC;
    public int ValueOfC
    {
        get { return valueOfC; }
        set
        {
            valueOfC = value;
            OnPropertyChanged(PropertyChanged);
        }
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventHandler handler)
    {
        if (handler != null)
            handler(this, new PropertyChangedEventArgs("ValueOfC"));
    }
}

I just tested it, and it works perfectly.

Having a protected virtual method fire the event for you is just common practice.

As a side note, if you want to do something if the lists change, you may want to look into using a BindingList or an ObservableCollection.

EDIT

I've written up a small example that does refresh the whole tree:

public class Bank : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private BindingList<Customer> customers = new BindingList<Customer>();
    public int Worth
    {
        get { return customers.Sum(cust => cust.FullBalance); }
    }

    public Bank()
    {
        customers.ListChanged += new ListChangedEventHandler(customers_ListChanged);
    }

    void customers_ListChanged(object sender, ListChangedEventArgs e)
    {
        Console.WriteLine("A customer has changed.");
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs("Worth"));
    }

    public void Add(Customer c) { customers.Add(c); }
}

public class Customer : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private BindingList<Account> accounts = new BindingList<Account>();
    public int FullBalance
    {
        get { return accounts.Sum(acc => acc.Balance); }
    }

    public Customer()
    {
        accounts.ListChanged += new ListChangedEventHandler(accounts_ListChanged);
    }

    void accounts_ListChanged(object sender, ListChangedEventArgs e)
    {
        Console.WriteLine("An account has changed.");
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs("FullBalance"));
    }

    public void Add(Account a) { accounts.Add(a); }
}

public class Account : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int balance = 0;
    public int Balance
    {
        get { return balance; }
        set
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs("Balance"));
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Account a1 = new Account() { Balance = 5 };
        Account a2 = new Account() { Balance = 10 };
        Account a3 = new Account() { Balance = 15 };

        Customer c1 = new Customer(); c1.Add(a1); c1.Add(a2);
        Customer c2 = new Customer(); c2.Add(a3);

        Bank b = new Bank(); b.Add(c1); b.Add(c2);

        Console.WriteLine();

        a1.Balance += 100;
    }
}

Now you can write something like if (e.ListChangedType == ListChangedType.ItemChanged) in the event handlers, or something similar.

Netfangled
  • 2,071
  • 1
  • 18
  • 28
  • How does that invalidate objects of class A and B that rely on C? – Hendrik Wiese Feb 10 '13 at 16:00
  • @SeveQ In your class `A` and `B` properties, you're always recalculating the total, so it always shows the up-to-date value. Plus, your `List` stores its values, I think, by reference, not by value, but don't quote me on that, I'm really not sure. If it's the case, when you change a value in your list, it is reflected in the list. – Netfangled Feb 10 '13 at 16:25
  • I should add that I'm talking about notifying WPF controls bound to A.ValueOfA and B.ValueOfB of changes in the underlying properties. When C notifies PropertyChanged für ValueOfC, ValueOfB and ValueOfA do not notify WPF of being changed as well and aren't refreshed on the GUI, if I remember correctly. – Hendrik Wiese Feb 11 '13 at 16:09
  • @SeveQ I seemed to have fixed the problem by changing the `List<>` for `BindingList<>` and subscribing empty handlers to the `ListChanged` event. I think it's because `ListChanged` has a null check before firing. – Netfangled Feb 11 '13 at 23:00
  • @SeveQ I've edited my post with an example of what I mean and I've found a question related to what you want: http://stackoverflow.com/questions/1427471/observablecollection-not-noticing-when-item-in-it-changes-even-with-inotifyprop/5256827#5256827 – Netfangled Feb 12 '13 at 03:11