我一直在一些C#代码上运行StyleCop,它不断报告我的using指令应该在命名空间中。

是否有技术原因将using指令放在命名空间内部而不是外部?


当前回答

通常,外部using指令(例如System和Microsoft命名空间)应该放在命名空间指令之外。除非另有规定,否则应在所有情况下应用默认值。这应该包括不属于当前项目的任何组织内部库,或者使用引用同一项目中其他主名称空间的指令。引用当前项目和命名空间中其他模块的任何using指令都应放在命名空间指令中。这有两个特定的功能:

它提供了本地模块和“其他”模块之间的视觉区别,这意味着所有其他模块。它将本地指令的范围限定为优先于全局指令应用。

后一个原因很重要。这意味着很难引入一个不明确的引用问题,而这个引用问题可能是由一个比重构代码更重要的更改引起的。也就是说,你将一个方法从一个文件移动到另一个文件,突然出现了一个以前不存在的bug。通俗地说,一个“海森堡”——历史上极难追踪。

其他回答

答案中讨论了技术原因,我认为这最终取决于个人偏好,因为差异没有那么大,而且两者都存在权衡。Visual Studio用于创建.cs文件的默认模板使用命名空间之外的指令,例如。

通过在项目文件的根目录中添加stylecop.json文件,可以调整stylecop以使用命名空间之外的指令进行检查,如下所示:

{
  "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace"
    }
  }
}

您可以在解决方案级别创建此配置文件,并将其作为“现有链接文件”添加到项目中,以便在所有项目中共享配置。

正如杰佩·斯蒂格·尼尔森(Jeppe Stig Nielsen)所说,这条线索已经有了很好的答案,但我认为这一相当明显的微妙之处也值得一提。

使用在名称空间中指定的指令可以缩短代码,因为它们不需要像在外部指定时那样完全限定。

以下示例之所以有效,是因为类型Foo和Bar都位于同一个全局命名空间Outer中。

假设代码文件Foo.cs:

namespace Outer.Inner
{
    class Foo { }
}

和Bar.cs:

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

这可能会省略using指令中的外部命名空间,简称:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

如果源解决方案中使用的默认引用(即“引用”)应该在命名空间之外,而那些“新添加的引用”是一个很好的做法,则应将其放在命名空间内。这是为了区分要添加的引用。

根据StyleCop文档:

SA1200:使用方向必须放置在命名空间中

原因C#using指令位于命名空间元素的外部。

规则说明如果将using指令或using别名指令放置在命名空间元素之外,则会违反此规则,除非文件不包含任何命名空间元素。

例如,以下代码将导致两次违反此规则。

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

但是,以下代码不会导致任何违反此规则的行为:

namespace Microsoft.Sample
{
    using System;
    using Guid = System.Guid;

    public class Program
    {
    }
}

这段代码将干净地编译,没有任何编译器错误。但是,尚不清楚分配的是Guid类型的哪个版本。如果将using指令移到命名空间内部,如下所示,将发生编译器错误:

namespace Microsoft.Sample
{
    using Guid = System.Guid;
    public class Guid
    {
        public Guid(string s)
        {
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            Guid g = new Guid("hello");
        }
    }
}

在包含Guid g=new Guid(“hello”)的行中发现以下编译器错误时,代码失败;

CS0576:命名空间“Microsoft.Sample”包含与别名“Guid”冲突的定义

该代码为System.Guid类型创建一个名为Guid的别名,并使用匹配的构造函数接口创建自己的类型Guid。稍后,代码将创建Guid类型的实例。要创建此实例,编译器必须在Guid的两个不同定义之间进行选择。当using alias指令置于命名空间元素之外时,编译器将选择在本地命名空间中定义的Guid的本地定义,并完全忽略在命名空间之外定义的using alis指令。不幸的是,这在阅读代码时并不明显。

但是,当using alias指令位于命名空间中时,编译器必须在同一命名空间中定义的两种不同的、冲突的Guid类型之间进行选择。这两种类型都提供了匹配的构造函数。编译器无法做出决定,因此会标记编译器错误。

将using alias指令放在命名空间之外是一种不好的做法,因为在这样的情况下,这可能会导致混淆,因为不清楚实际使用的是哪种类型的版本。这可能会导致难以诊断的错误。

在命名空间元素中放置使用别名指令可以消除这一错误源。

多个命名空间

在一个文件中放置多个名称空间元素通常是一个坏主意,但如果这样做了,最好将所有using指令放置在每个名称空间元素中,而不是全局放置在文件的顶部。这将严格限定命名空间的范围,也将有助于避免上述行为。

需要注意的是,当使用放置在命名空间外部的指令编写代码时,在命名空间内移动这些指令时应小心,以确保这不会改变代码的语义。如上所述,在命名空间元素中放置使用别名指令允许编译器在冲突类型之间进行选择,而当指令放置在命名空间之外时,这种选择不会发生。

如何修复违规要修复违反此规则的问题,请在命名空间元素中移动所有using指令和using别名指令。

事实上,两者之间有(微妙的)区别。假设您在File1.cs中有以下代码:

// File1.cs
using System;
namespace Outer.Inner
{
    class Foo
    {
        static void Bar()
        {
            double d = Math.PI;
        }
    }
}

现在想象一下,有人将另一个文件(File2.cs)添加到项目中,如下所示:

// File2.cs
namespace Outer
{
    class Math
    {
    }
}

编译器在查看命名空间外使用指令的对象之前搜索Outer,因此找到Outer.Math而不是System.Math。不幸的是(或者幸运的是?),Outer.Math没有PI成员,因此File1现在已损坏。

如果将using放在命名空间声明中,则会发生如下变化:

// File1b.cs
namespace Outer.Inner
{
    using System;
    class Foo
    {
        static void Bar()
        {
            double d = Math.PI;
        }
    }
}

现在编译器先搜索System,再搜索Outer,找到System.Math,一切正常。

有些人会认为Math对于用户定义的类来说可能是个坏名字,因为System中已经有一个;这里的重点是存在差异,这会影响代码的可维护性。

如果Foo位于命名空间Outer,而不是Outer.Inner,会发生什么也很有趣。在这种情况下,在File2中添加Outer.Math会破坏File1,而不管使用的位置如何。这意味着编译器在查看任何using指令之前搜索最内部的封闭命名空间。