1

I have a recursive hierarchy of three kinds of object in a C# library. Let's call them Boxes, Nuts and Bolts. Boxes can contain other Boxes, or Nuts and Bolts. Nuts and Bolts obviously can't contain anything.

Let's assume each Box has ObservableCollections of Box, Nut, and Bolt. Each Nut and Bolt implements INotifyPropertyChanged.

Is there an accepted best practice for propagating notifications of changes to the observable collections, or property changes on any Nut or Bolt to an object which holds a reference to the topmost Box? Or any particular design patterns you would recommend?

EDIT: to give some background to this issue, I lead the Chemistry for Word project. You can see the component that display structures in real time on the left. Chem4Word Navigator Now, believe it or not, this currently draws everything through data binding. Each of those molecule displays on the LHS is an ItemsControl. (And yes, I am using WPF with MVVM!) This has proven to have too many overheads and lack of flexibility for a long-term solution. So I have gone back to generating DrawingVisuals directly. This approach allows much more fine control. The Boxes, Nuts and Bolts in my original example are Molecules, Atoms and Bonds. If any of these are added, removed or changed then the display has to know about it so it can update. As I have already implemented the interfaces and objects for data binding then I want to exploit the code I already have.

deadlyvices
  • 873
  • 6
  • 18
  • Why the question? What is the actual problem? If you bind to `Nut` you don't need to bubble the change all the way up to `Box` or the `ObservableCollection`. You shouldn't need to reload and redraw all boxes just to update the UI element that binds to a `Nut`. Do you modify the UI directly instead of using data binding perhaps? Or are you using one-way binding for `Nut`? – Panagiotis Kanavos Jan 10 '19 at 15:10
  • @dymanoid INotifyPropertyChanged and ObservableCollection are used mainly in WFP, XAML, UWP. Winforms depended on events instead and ASP.NET doesn't care about server-side events. Even if the question *isn't* about one of the XAML-related technologies, what is the actual problem? – Panagiotis Kanavos Jan 10 '19 at 15:16
  • What is the problem? How to find the parent? `INotifyPropertyChanged` is perfect choice, the parent can use it. But `ObservableCollection` will not monitor for its children. It's your role or (if you are talking about wpf) that's what binding will do. Depending on the task you may consider another design, when each child get reference of its parent and is able to invoke method of parent upon change. – Sinatr Jan 10 '19 at 15:16
  • The actual problem is that I am displaying diagrams of molecules which contain atoms, bonds and other molecules. I tried data binding and had it working for a while in production. The overheads and lack of flexibility mean I have had to go back to the drawing board, drawing the corresponding shapes directly onto a Canvas. So I already have INotifyPropertyChanged on each object. I want to notify the top levelm object of all changes to the data model so I can update accordingly – deadlyvices Jan 10 '19 at 21:29
  • See my edit to the original posting. – deadlyvices Jan 10 '19 at 21:45
  • 1
    Let each container-of-things also be a factory of it's contained things...and then let the contained things have weak references to their containers. Then any leaf or branch node has a lightning fast up-propagating communications channel...with a null-safe calls: `parent?.SomethingHappenedTo( originatingNode );` – Clay Jan 10 '19 at 22:15

3 Answers3

2

I've had a similar model with fast-path access to upstream Node instances in directed acyclic graphs. A Node has a weak reference to its immediate parent. Node has a property to get the Root...that tries to return its parent's Root. If there's no parent, then that node is a root. Rootness is based solely on containment. Note that the parent isn't a collection...because sometimes the child node isn't even in a collection. Something more-or-less like...

public abstract class Node
{
  WeakReference<Node> parent;

  public Node Root
  {
    get { return Parent?.Root ?? this; }
  }

  public Node Parent
  {
    get
    {
      if ( parent != null )
      {
        if ( parent.TryGetTarget( out Node parentNode ) )
        {
          return parentNode;
        }
      }
      return this;
    }
    internal set { /*...*/ } //--> if you're brave...
  }
}

Edit:

Regarding WeakReferences...one of the things our graphs can have is references to nodes in other graphs. We have a node resolver service that will fetch those other nodes. These out-looking references are represented by an identity value (a GUID or a Long) and an associated weak reference. This way, we can load the specified node as needed, but not keep it any longer than necessary. The resolver maintains an LRU cache of nodes resolved this way.

If such a resolved reference needs to resolve its own parent, a similar mechanism exists to allow the dependent node to resolve its parent. Even a node's collected child nodes may be lazy loaded via the resolver service (although there are annotations that inform our framework when to lazy-load and when not).

So, the weak references help with all these incidentally-resolved scenarios. Er...more accurately, they help us not screw up the garbage collection in such scenarios.

In some analytical scenarios, we'll have hundreds of thousands of nodes popping in and out. I could imagine similar dynamics in chemistry modeling.

Clay
  • 4,999
  • 1
  • 28
  • 45
  • Quite a few people have mentioned weak references. What are the advantages of using those over strong ones? – deadlyvices Jan 12 '19 at 06:37
  • 1
    Weak references are useful in scenarios where A references B and B references C and C references A. This can gum up the garbage collector. If you have strong references downward through three, but weak references upward, you don't have to do anything weird to facilitate garbage collection. When you detach a branch of nodes from the tree it will get cleanly collected. – Clay Jan 12 '19 at 13:52
  • 1
    Having said that, if a whole tree goes out of scope, I believe the GC can still resolve all those references and collect everything. It may be a bigger deal in other deterministic GCs like in Swift...so it might be overkill here. – Clay Jan 12 '19 at 13:57
  • Still worth investigating. These aren't, in principle, big object trees. But they could be messy. Thank you for your useful comments. – deadlyvices Jan 13 '19 at 13:43
  • Thinking about it, I can see certain scenarios where we require references to nodes in other graphs. So the WeakReferences are even more applicable. – deadlyvices Jan 14 '19 at 14:00
  • The other nice thing about this whole scenario is how it serializes. You can serialize a graph and easily reconstitute it, because the serialized nodes have their Ids and parent Ids...and you have a resolver service to fetch nodes by Id. So you can be "lazy" about out-of-graph references. How a graph serializes and deserializes is a big ol' deal and worth putting some cycles into. – Clay Jan 14 '19 at 15:27
  • Indeed, that is an excellent point. The current model we use is a nightmare to serialize easily. – deadlyvices Jan 14 '19 at 16:59
1

Why don't you call the parent in the change notification. Something like the following pseudo code:

Bolt()
{
    NotifyPropertyChanged(property)
    {
         PropertyChanged(this, property);
    }

    ChildNutPropertyChanged(Nut child, property)
    {
         PropertyChanged(this, child + property);
    }
}


Nut(Bolt parentBolt)
{ 
    parent = parentBolt;

    NotifyPropertyChanged(property)
    {
         PropertyChanged(this, property);
         parent.NotifyPropertyChanged(this, property);
    }
}
Murray Foxcroft
  • 12,785
  • 7
  • 58
  • 86
1

If you encapsulate your ObservableCollection of Nuts and Bolts and only make a ReadOnlyObservableCollection public, you could make an Add(Nut nut) method (and another one for bolts) that registers to the added Nut's NotifyPropertyChanged event.

This way, you'll know in the Box when a property of a child has changed and take action.

Gimly
  • 5,975
  • 3
  • 40
  • 75