views:

141

answers:

4

I have a situation where an object A has a reference to object B. B also has a reference back to A. For the sake of simplicity, let's say that A and B are of the same type. How do I ensure that when I update the reference on A, that the update will be reflected on B as well (and vice versa)?

An example of an interface of such a type:

interface IGraphNode
{
    IGraphNode From { get; set; }
    IGraphNode To { get; set; } 
}

After executing the code below, I would expect B.From to return A.

IGraphNode A = new GraphNode();
IGraphNode B = new GraphNode();
A.To = B;
A: 

You have to explicitly set:

A.To = B;
B.From = A;

As far as this being possible, it is. Most object oriented languages transparently deal with cycling references such as these.

If you're looking to make this bi-directional referencing automatic, you could always extend your IGraphNode interface and make an abstract base class that has some code in your From and To properties which sets the other's value when modified. All GraphNodes that require this automatic functionality should extend the base abstract class.

Soviut
Ideally it should be automatic - how would you implement this?
Eric Smith
Don't you mean "Most object oriented languages don't transparently deal with cycling references such as these"?
Eric Smith
What I meant was, platforms like .NET don't choke if you have A refer to B and B refer to A. These sorts of "has a" relationships are common and the compilers can all handle them.
Soviut
+2  A: 
IGraphNode From
{ 
    get { return from; }
    set 
    {
        from = value;
        if (value.To != this) {
            value.To = this;
        }
    }
}

IGraphNode To
{
    get { return to; }
    set
    {
        to = value;
        if (value.From != this) {
            value.From = this;
        }
    }
}

You might want to extend it with a check if (value == this)...

However, if you enforce the circular reference you can never use IGraphNode for non-circular things...

EricSchaefer
You could use a method like `nodeA.Connect(nodeB)`. It would be more symmetric and you could use `.To` and `.From` for non-circular things.
Jules
Why is the check? Why not just "value.From = this;"?
Hosam Aly
To prevent Recursion: If you set A.To=B ==> B.From=A ==> A.To=B and so on...
EricSchaefer
A: 

You'll need to have A notify B of the link, possibly by having it simply set the B.From property.

However, you need to think about things like what if more than one object gets a link to B:

IGraphNode A = new GraphNode();
IGraphNode B = new GraphNode();
IGraphNode C = new GraphNode();

A.To = B;
C.To = B;

// what should B.From return?

Also, think about what happens if one of the referenced objects is otherwise unreferenced? For example:

IGraphNode A = new GraphNode();
IGraphNode B = new GraphNode();

A.To = B;
A = null;  // should the reference from B keep the object 
           // that A used to reference alive?

A WeakReference object instead of a direct reference might help with this particular problem.

Michael Burr
A: 

While the accepted answer is clever, I'm not a fan of it. In particular, the part about modifying 'value' inside a property. Methods/Properties should not have hidden side-effects. Practices like this usually result in unexpected behavior that is difficult to track down. It is not natural to expect that calling a property on A would have modified my B object. I would only know this if I explicitly examined the code? Many times the source code is not even available.

A better solution would be to create an explicit method that made the operation and any side-effects more obvious such as:

void ConnectNodes(IGraphNode a, IGraphNode b)
{
    a.to = b;
    b.from = a;
}

or

void ConnectNodes(IGraphNode b)
{
    this.to = b;
    b.from = this;
}
Dunk