16

This probably sounds like a stupid question, but I'm going to give it a shot anyway.

So in Visual Studio, you can't have two projects X and Y such that X references Y and Y references X.

In general, I can totally understand how having a circular dependency can be problematic, for a variety of reasons.

But is it really not possible to compile two projects that are interdependent in this way? It seems to me that it must be possible, since (in my mind -- maybe I'm completely off-base about this) having two mutually dependent assemblies is really not so different from having two mutually dependent classes -- a case which is legal and can be compiled.

It would make sense to me if you said, "two assemblies cannot depend on each other because the compiler could not compile one before the other"; except that it seems you could make the same argument for two classes within the same assembly, and clearly the compiler can deal with this scenario just fine.

Basically the reason I'm asking is not that I have some desperate desire to do this thing that I know is generally ill-advised anyway. Specifically I'm wondering because it would be nice if I could have two projects -- say, MyProjectCS and MyProjectVB -- that existed basically as two mutually dependent parts of a single unit, and were only separate because certain parts were written in C# and other parts were written in VB.NET.

So, my question is (yikes, three-fold):

  1. Is it possible to enable this behavior (in Visual Studio, or elsewhere, for that matter)?
  2. If it's not possible within any IDE, is it at least theoretically possible, or could mutually dependent assemblies not possibly exist?
  3. If it's not even theoretically possible, why not? In other words, how are mutually dependent assemblies different from mutually dependent code within a single assembly?
Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • 7
    This happens to me all the time... my Egg project throws 'Chicken.dll not found...' while my Chicken project throws a similar error. Ho-hum. – Charlie Salts Jul 03 '10 at 01:27
  • 2
    The .NET framework internally uses mutually dependent assemblies. Someone found out a while ago after disassembling the .NET assemblies and posed that question on SO (can't find the link though). – Alex Jul 03 '10 at 01:28
  • @Alex yeah I found that once. It looked to me like it did it via reflection. – Joshua Jul 03 '10 at 01:29
  • @Charlie Salts: Are you poking fun at the question? I get what you're saying but I thought I addressed that argument in what I wrote. – Dan Tao Jul 03 '10 at 01:30
  • Just bringing some levity to an interesting question ;) I'm looking forward to some great answers. +1 – Charlie Salts Jul 03 '10 at 01:41

5 Answers5

12

I don't know how to do it in an IDE; however it is possible to construct via a compilicated build process.

You will need:

  1. Assembly A
  2. Assembly B
  3. Stub Assembly B

where Stub Assembly B contains the public classes and public methods of Assembly B and the same AssemblyInfo.* and references the same public key.

Build order:

  1. Compile Stub Assembly B
  2. Copy Stub Assembly B to the output dir of Assembly B
  3. Build assembly A
  4. Build assembly B

Notice that you cannot have direct loop references of the types in the method signatures; however you can have effective loops by casting through object.

NOTE:

ilasm can compile true mutually recursive assemblies as somehow it can resolve types that don't exist at compile time.

FURTHER:

the aspnet_compiler seems to be able to mix different languages in the same project (who knows how).

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 3
    You can have loops in the types of the method signatures using an even more complicated build process: first define all classes and compile that, then set up the inheritance hierarchy (using the stubs from the previous step) and add all non-override methods (but without bodies), and in a third pass finally do a normal compilation (now finally including method bodies). If you really want to do this, #if will be your new best friend. – Daniel Jul 03 '10 at 01:35
6

Even though mscorlib.dll and System.dll assemblies are mutually dependent, I'd advise to never have 2 assemblies mutually dependent.

Concerning dependency cycles between hings like namespaces, I'd advise using NDepend to detect and avoid dependency cycles.

alt text

Excerpt from the article (I wrote): Control component dependencies to gain clean architecture

Dependency cycles between components lead to what is commonly called spaghetti code or tangled code. If component A depends on B that depends on C that depends on A, the component A can’t be developed and tested independently of B and C. A, B and C form an indivisible unit, a kind of super-component. This super-component has a higher cost than the sum of the cost over A, B and C because of the diseconomy of scale phenomenon (well documented in Software Estimation: Demystifying the Black Art by Steve McConnell). Basically, this holds that the cost of developing an indivisible piece of code increases exponentially.

This suggests that developing and maintaining 1,000 LOC (Lines Of Code) will likely cost three or four times more than developing and maintaining 500 LOC, unless it can be split in two independent lumps of 500 LOC each. Hence the comparison with spaghetti that describes tangled code that can’t be maintained. In order to rationalize architecture, one must ensure that there are no dependency cycles between components, but also check that the size of each component is acceptable (500 to 1000 LOC).

Patrick from NDepend team
  • 13,237
  • 6
  • 61
  • 92
  • 2
    A very thoughtful and informative response. I just want to point out, however, that I was mainly asking this question from the perspective of wanting to be able to write a component in a combination of C# and VB.NET. So though I get what you mean by "super-component," I feel that it doesn't quite match what I was after. To me a "super-component" would be the combination of multiple mutually dependent components; a "mixed-component," as I'll call it, on the other hand, would be the decomposition (by language) of a *single* component into mutually dependent *parts*. Does that make sense? – Dan Tao Aug 29 '10 at 19:19
  • You should choose VB.NET or C#, and migrate the other language to the language choosen (with the help of .NET Reflector for example, that can convert C# to VB.NET code or vice-versa). There can't be good reasons for maintaining 2 languages to code the same assembly. – Patrick from NDepend team Aug 30 '10 at 06:51
2

I do not know how it would work in VB, but theoretically it should be possible to use some kind of placeholder pointing at the other (generating illegal code) for compiling one of them, and then use that to compile the other, and then recompile the first.

That is how, for example, circular dependency resolution works when compiling programs that require each other.

--Though usually that's done by disabling the features that don't exist yet

zebediah49
  • 7,467
  • 1
  • 33
  • 50
  • 3
    FYI, in native C and C++ we accomplish this by virtue of header files. – Joshua Jul 03 '10 at 01:30
  • @Joshua, this works if both interdependent things are compiled and linked at the same time. The answer’s description of “disabling the features that don't exist yet” is often used to break cycles at the package level with the help of a package manager. – binki Jul 07 '14 at 17:45
1

If you build using command line tools, you can have an assembly that contains many module. Each module can be compiled with a different compiler. Modules can have circular dependances between them.

However I don't expect visiual studio to ever surport this.


There are also trick you can do that will tell the linker to redirect a request for a type from one assembly to another. Microsoft uses these then they move types within the .net framework. This is only of value if you can’t getter all your callers to recompile there code.

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
0

It is possible to have circular dependencies in Visual Studio if you use conditional compilation. Most of the time it would be best to remove the circular reference to begin with, but if you have a good reason to keep them, this solution could be used as a workaround to get it to build.

Community
  • 1
  • 1
TTT
  • 22,611
  • 8
  • 63
  • 69