为什么 C# 中的命名空间允许循环依赖?

Why is circular dependency allowed with namespaces in c#?

在 c# 中,您可以在文件 a.cs(具有 MyApp.A 的命名空间)中有一个语句:

using MyApp.B;

而文件 b.cs(具有 MyApp.B 的命名空间)已经有语句

using MyApp.A;

如果不同的 dll 中存在类似的依赖关系(其中 a.dll 引用了 b.dll,反之亦然)由于循环依赖错误,它不会被允许,那么为什么会这样允许命名空间(编译器甚至不产生警告)?这样做是不是有点代码味?

您关于程序集不能相互引用的假设是不正确的。设置起来很痛苦,通过 Visual Studio、IIRC 也不容易,但它是可行的。

首先,我们创建 3 个文本文件:

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)
    {
    }
  }
}

并且,A_prime.cs

namespace A
{
  public class AA
  {
    public AA()
    {
    }
  }
}

请注意,这些 类 并不意味着有用或可用。只是为了证明这一点——我们有两个可能的 A 来源——一个是我们剔除或 "work around" 我们对 B 的依赖的需要,另一个是我们不要。

我们接着编译:

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

最终结果 - 我们有两个相互交叉引用的程序集1.

同样,在 .NET 框架本身内,我们有大量的交叉引用发生 - System.dllSystem.Xml.dllSystem.Configuration.dll 都是相互交叉引用。虽然他们可能有比上面更复杂的构建设置 - 使用单个源文件和符号来执行引导,而不是单独的源文件。

最后,要解决命名空间问题 - 命名空间是一个合乎逻辑 的功能分组。多个程序集可以将类型贡献给一个命名空间。单个程序集可以为多个命名空间提供类型。没有 合乎逻辑的 原因说明为什么多个名称空间中的类型不能在两个方向上相互引用2


1如果 csc 版本没有 AssemblyVersion 属性,您使用默认分配版本号 0.0.0.0在源代码中。如果 csc 的特定实例不是这种情况(我似乎记得它在某些较旧的实例上有所不同),您可能需要应用此属性以确保 A.dll 的两个编译产生一个具有相同标识的程序集。

同样,强命名等细节可能会使过程更加费力。

2"Little-r" 参考,不是 "Big-r" 参考。例如。不同类型只是互相使用。未参考 "What is a reference in C#?" 条款。

Damien_The_Unbeliever 写道 命名空间是功能的逻辑分组

Hans Passat 写道 命名空间只不过是对编译器的简单提示

我想详细说明一下,这实际上取决于您在代码库中将什么视为 组件component 是一组类型。 组件 可以生成一个或多个在一个或多个程序集中定义的名称空间。重要的是组件之间没有依赖循环。因为一个组件是一个开发单元,如果A组件和B组件相互利用,就构成一个更大的开发单元,它们不能独立开发,它们组成了一个超级组件 或者说,这就是意大利面条代码.

的根源

为了回答您的问题,为什么 C# 中的名称空间允许循环依赖?您的问题背后的 implicit 声明是名称空间是用于定义 逻辑组件 。但是命名空间也可以用来避免类型名称冲突,以结构化的方式呈现 public API 的 类,过滤一组扩展方法......因此我猜答案是 C# 设计者当然不想限制命名空间的概念只用于逻辑组件化

附带说明一下,许多开发人员使用 project/assembly 的概念来定义组件。恕我直言,这是错误的,因为装配是一个物理概念,伴随着成本和维护(版本控制、部署、编译、动态 CLR 加载……)。出于 物理 的原因(例如部署单元、[=73= 单元]、插件实现、code/test分离...)。我写了 two white books 关于这个 assembly vs namespace vs component 话题如果你感兴趣的话。


这样做是不是有点代码味?

我认为是的,因为如果一个大型程序集包含许多具有依赖循环的名称空间,则无法尝试从代码中理解整体架构。我在一个名为 NDepend 的 .NET 静态分析器上工作,它可以检查 namespaces dependency cycles and present results through dependency graph and dependency matrix。我们有 400 多个命名空间,我们很高兴将它们正确地分层放置在十几个程序集中。依赖矩阵提供了一种方便的方式来一目了然地可视化分层命名空间结构。