Here are a few examples that can help illustrate why circular dependencies are bad.
Problem No. 1: What is initialized / built first?
Consider the following example:
class A { public A() { myB.DoSomething(); } private B myB = new B(); } class B { public B() { myA.DoSomething(); } private A myA = new A(); }
Which constructor is called first? There really is no way to be sure, because it is completely ambiguous. One or the other DoSomething method will be called on an object that is not initialized., Which leads to incorrect behavior and, most likely, an exception. There are ways around this problem, but they are all ugly, and they all require non-constructor initializers.
Problem number 2:
In this case, I changed to uncontrolled C ++ - an example, because the .NET design implementation hides the problem from you. However, in the following example, the problem becomes quite clear. I am well aware that .NET does not use reference counting under the hood to manage memory. I use it here only to illustrate the main problem. We also note that I have demonstrated here one possible solution to problem No. 1.
class B; class A { public: A() : Refs( 1 ) { myB = new B(this); }; ~A() { myB->Release(); } int AddRef() { return ++Refs; } int Release() { --Refs; if( Refs == 0 ) delete(this); return Refs; } B *myB; int Refs; }; class B { public: B( A *a ) : Refs( 1 ) { myA = a; a->AddRef(); } ~B() { myB->Release(); } int AddRef() { return ++Refs; } int Release() { --Refs; if( Refs == 0 ) delete(this); return Refs; } A *myA; int Refs; };
At first glance, you might think that this code is correct. The reference counting code is pretty straightforward. However, this code causes a memory leak. When A is built, it first has a reference count of "1". However, the encapsulated variable myB increments the reference counter, assigning it a counter of "2". When localA is freed, the counter decreases, but returns only to "1". Consequently, the object remains hanging and is never deleted.
As I mentioned above, .NET does not really use reference counting for its garbage collection. But it uses similar methods to determine if an object is still being used, or if itβs OK, to remove it, and almost all such methods may become confused with circular references. The .NET garbage collector claims to be able to handle this, but I'm not sure I trust it, because it is a very complex problem. On the other hand, the problem is related to the problem, simply not allowing circular links at all. Ten years ago, I would have preferred the .NET approach for its flexibility. These days, I prefer the Go approach for its simplicity.