问题
In c# you're allowed to have a statement in file a.cs (which has namespace of MyApp.A):
using MyApp.B;
while file b.cs (which has namespace of MyApp.B) already have the statement
using MyApp.A;
If a similar dependency would exist in different dll's (where a.dll has a reference to b.dll and vice versa) it wouldn't be allowed because of circular dependency error, so why is it allowed with namespaces (and compiler doesn't even produce a warning)? Isn't it a code smell to do so anyway?
回答1:
Damien_The_Unbeliever wrote Namespaces are a logical grouping of functionality.
Hans Passat wrote Namespace is nothing but a simple hint to the compiler.
I'd like to elaborate that it really depends what you consider being a component in your code base. A component is a group of types. A component can spawn one or several namespaces defined in one or several assemblies. The important thing is that there is no dependency cycle between components. Because a component is a unit of development, and if component A and B are mutually using each other, they constitute a bigger unit of development, they cannot be developed independently, they form a super-component or in other words, this is the root of spaghetti code.
To answer your question, Why is circular dependency allowed with namespaces in c#? the implicit statement behind your question is that namespaces are used to define logical components. But namespace can also be used to avoid type name collision, to present classes of a public API in a structured way, to filter a set of extension methods... Hence I guess the answer is that the C# designer certainly didn't want to restrict the concept of namespace only for logical componentization.
As a side note, many developers are using the concept of project/assembly to define components. IMHO this is wrong because assembly is a physical concept that comes with cost and maintenance (versioning, deployment, compilation, dynamic CLR load...). Assembly as a physical concept should be used for physical reasons (like unit of deployment, unit of API, plugin impl, code/test separation...). I wrote two white books on this assembly vs namespace vs component topic if it can interest you.
Isn't it a code smell to do so anyway?
In my opinion it is, because if a large assembly contains many namespaces with dependency cycles, one has no way to try understanding the overall architecture from code. I work on a .NET static analyzer named NDepend that can check for namespaces dependency cycles and present results through dependency graph and dependency matrix. We have more than 400 namespaces and we are glad to keep them all layered properly in a dozen of assemblies. The dependency matrix offers a handy way to visualize the layered namespace structure at a glance.
回答2:
Your assumptions that assemblies cannot cross reference each other is incorrect. It's a pain to set up, and not easy to do through Visual Studio, IIRC, but it's doable.
First, we create 3 text files:
A.cs
:
using B;
namespace A
{
public class AA
{
public AA(BB val)
{
}
}
}
B.cs
:
using A;
namespace B
{
public class BB
{
public BB(AA val)
{
}
}
}
And, A_prime.cs
:
namespace A
{
public class AA
{
public AA()
{
}
}
}
Note that these classes are not meant to be useful or usable. Just to demonstrate the point - which is that we have two possible sources for A
- one in which we stub out or "work around" our need for a dependency on B
, and the other where we do not.
We then compile:
csc /out:A.dll /target:library A_prime.cs
csc /out:B.dll /target:library /reference:A.dll B.cs
csc /out:A.dll /target:library /reference:B.dll A.cs
End result - we have two assemblies that cross-reference each other1.
Similarly, within the .NET framework itself, we have plenty of cross-referencing happening - System.dll
, System.Xml.dll
and System.Configuration.dll
are all mutually cross-referencing. Although they probably have a more sophisticated build setup than the above - using a single source file and symbols to perform the bootstrapping, rather than separate source files.
And finally, to address namespaces - Namespaces are a logical grouping of functionality. Multiple assemblies may contribute types to a single namespace. A single assembly can contribute types to multiple namespaces. There is no logical reason why types within multiple namespaces should not be able to reference2 each other in both directions.
1This works provided the version of csc
you're using defaults to assigning a version number of 0.0.0.0 if there's no AssemblyVersion
attribute in the source. If that's not the case for a particular instance of csc
(I seem to remember it was different on some older ones), you may need to apply this attribute to ensure that the two compilations of A.dll
produce an assembly with the same identity.
Similarly, details like strong naming may make the process more laborious.
2"Little-r" reference, not "Big-r" reference. E.g. different types just using each other. Not reference in "What is a reference in C#?" terms.
来源:https://stackoverflow.com/questions/37829263/why-is-circular-dependency-allowed-with-namespaces-in-c