关于C#:”using”指令应该在命名空间内部还是外部?

Should 'using' directives be inside or outside the namespace?

我已经在一些C代码上运行了Stylecop,它不断报告我的using指令应该在名称空间中。

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


这两者实际上有一个(细微的)区别。假设您在file1.cs中有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
// File1.cs
using System;
namespace Outer.Inner
{
    class Foo
    {
        static void Bar()
        {
            double d = Math.PI;
        }
    }
}

现在假设有人向项目中添加了另一个文件(file2.cs),如下所示:

1
2
3
4
5
6
7
// File2.cs
namespace Outer
{
    class Math
    {
    }
}

编译器先搜索Outer,然后再查看命名空间外的那些using指令,因此它找到Outer.Math,而不是System.Math。不幸的是(或者幸运的是?),Outer.Math没有PI成员,因此文件1现在被破坏。

如果将using放在名称空间声明中,则会发生以下更改:

1
2
3
4
5
6
7
8
9
10
11
12
// File1b.cs
namespace Outer.Inner
{
    using System;
    class Foo
    {
        static void Bar()
        {
            double d = Math.PI;
        }
    }
}

现在编译器在搜索Outer之前先搜索System,然后找到System.Math,一切都很好。

有些人会争辩说,对于用户定义的类,Math可能是一个坏名字,因为System中已经有一个;这里的要点只是有一个区别,它影响代码的可维护性。

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


这条线索已经有了一些很好的答案,但是我觉得我可以用这个额外的答案带来更多的细节。

首先,记住带有句点的名称空间声明,例如:

1
2
3
4
namespace MyCorp.TheProduct.SomeModule.Utilities
{
    ...
}

完全等同于:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace MyCorp
{
    namespace TheProduct
    {
        namespace SomeModule
        {
            namespace Utilities
            {
                ...
            }
        }
    }
}

如果你愿意,你可以把using指令放到所有这些级别上。(当然,我们只想在一个地方使用using,但根据语言,这是合法的。)

解析隐含哪种类型的规则可以这样松散地表述:首先搜索最内部的"范围"以查找匹配项,如果找不到任何匹配项,则从一个级别到下一个范围再搜索,依此类推,直到找到匹配项为止。如果在某个级别上找到多个匹配项,如果其中一个类型来自当前程序集,请选择该类型并发出编译器警告。否则,放弃(编译时错误)。

现在,让我们在两个主要约定的具体示例中明确说明这意味着什么。

(1)外部使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections.Generic;
using System.Linq;
//using MyCorp.TheProduct;  <-- uncommenting this would change nothing
using MyCorp.TheProduct.OtherModule;
using MyCorp.TheProduct.OtherModule.Integration;
using ThirdParty;

namespace MyCorp.TheProduct.SomeModule.Utilities
{
    class C
    {
        Ambiguous a;
    }
}

在上述情况下,为了找出Ambiguous是什么类型,搜索按以下顺序进行:

  • C中的嵌套类型(包括继承的嵌套类型)
  • 当前命名空间中的类型MyCorp.TheProduct.SomeModule.Utilities
  • 命名空间MyCorp.TheProduct.SomeModule中的类型
  • MyCorp.TheProduct中的类型
  • MyCorp中的类型
  • 空命名空间(全局命名空间)中的类型
  • SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty中的类型
  • 另一公约:

    (2)内部使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    namespace MyCorp.TheProduct.SomeModule.Utilities
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using MyCorp.TheProduct;                           // MyCorp can be left out; this using is NOT redundant
        using MyCorp.TheProduct.OtherModule;               // MyCorp.TheProduct can be left out
        using MyCorp.TheProduct.OtherModule.Integration;   // MyCorp.TheProduct can be left out
        using ThirdParty;

        class C
        {
            Ambiguous a;
        }
    }

    现在,按以下顺序搜索Ambiguous类型:

  • C中的嵌套类型(包括继承的嵌套类型)
  • 当前命名空间中的类型MyCorp.TheProduct.SomeModule.Utilities
  • SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProductMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty中的类型
  • 命名空间MyCorp.TheProduct.SomeModule中的类型
  • MyCorp中的类型
  • 空命名空间(全局命名空间)中的类型
  • (请注意,MyCorp.TheProduct是"3"的一部分,因此在"4"和"5"之间不需要。)

    结束语

    无论您将using放在名称空间声明的内部还是外部,总会有可能稍后有人向其中一个具有更高优先级的名称空间添加具有相同名称的新类型。

    此外,如果嵌套命名空间与类型同名,则可能会导致问题。

    将using从一个位置移动到另一个位置总是很危险的,因为搜索层次结构会更改,并且可能会找到另一种类型。因此,选择一个约定并坚持它,这样你就不必移动using了。

    默认情况下,Visual Studio的模板将using放在命名空间之外(例如,如果使vs在新文件中生成新类)。

    外部使用的一个(微小)好处是,您可以使用全局属性的使用指令,例如[assembly: ComVisible(false)],而不是[assembly: System.Runtime.InteropServices.ComVisible(false)]


    将它放在名称空间中会使该文件的名称空间的声明成为本地声明(如果文件中有多个名称空间),但是如果每个文件只有一个名称空间,那么无论是放在名称空间之外还是放在名称空间内部,都不会有太大的区别。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    using ThisNamespace.IsImported.InAllNamespaces.Here;

    namespace Namespace1
    {
       using ThisNamespace.IsImported.InNamespace1.AndNamespace2;

       namespace Namespace2
       {
          using ThisNamespace.IsImported.InJustNamespace2;
       }      
    }

    namespace Namespace3
    {
       using ThisNamespace.IsImported.InJustNamespace3;
    }


    根据汉塞尔曼使用指令和装配装载…而其他这类文章在技术上没有区别。

    我的首选是将它们放在名称空间之外。


    根据这一stylecop文档:

    sa1200:usingdirectivesmustbeplacedwithinnamespace

    因为a c #使用指令是位于外a命名空间的元素。

    规则描述违反本规则时当使用指令或指令使用别名命名空间元素A是放置外,除非文件不包含任何元素的命名空间。

    例如,下面的代码将两个违反本规则。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    using System;
    using Guid = System.Guid;

    namespace Microsoft.Sample
    {
        public class Program
        {
        }
    }

    然而,下面的代码将不会导致任何违反本规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    namespace Microsoft.Sample
    {
        using System;
        using Guid = System.Guid;

        public class Program
        {
        }
    }

    本代码编译cleanly想,没有任何编译器错误。然而,它是不清楚这版本GUID类型是分配。如果使用额外的指令是在一个命名空间,显示下面的编译错误发生,一想:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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"别名"conflicting的GUID

    在一个系统的代码创建的别名称为GUID。GUID类型和创建自己的类型,所以所谓匹配的GUID接口一个构造函数。之后,创建实例的代码类型的GUID。创建这个实例,编译器必须选择两种不同的定义,在学院的GUID。当使用别名指令是放置外面的命名空间元素,要选择本地编译器定义GUID定义在名字空间中的使用,完全忽略别名指令定义的命名空间的外面。不幸的是,这不是显而易见的,当阅读的代码。

    当使用别名命名空间指令是放置在一个编译器,但是,安切洛蒂之间选择两种不同的类型,conflicting GUID都定义在相同的命名空间。本文提供了一个匹配的两个类型的构造函数。编译器是无法作出一个决策,那么它的旗帜的编译器错误。

    使用别名指令放在浴室外的命名空间,因为它是一个实践中导致混乱的情况下,如本,它是不明显的,版本类型实际上是被使用。这可以导致潜在的错误可能是很难诊断。

    使用别名指令放在命名空间的元素eliminates本作为一个来源的错误。

  • 多个命名空间
  • 多元素放置在一个单一的命名空间的文件通常是坏主意,但如果当这是完成,这是一个好主意,在每个地方都使用指令的全局命名空间的元素,而在顶部的文件。这将显示在tightly和命名空间,所以帮助孩子想去避免上面描述的行为。

    它是重要的注意,当一个指令的代码已被使用的命名空间外,护理应采取的指示,当运动论文在命名空间,这是确保在不改变语义的代码。为解释以上使用别名指令,将在编译一个命名空间的元素可以conflicting之间选择一个需要的类型的方式,当指令是放置外面的命名空间。

    如何修复违反固定违反本规则,把所有的指令和指令的使用使用的别名在命名空间的元素。


    有问题的语句放在一个命名空间,当你想使用别名。"别名不受益从早期的using语句和安切洛蒂是完全合格的。

    考虑:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    namespace MyNamespace
    {
        using System;
        using MyAlias = System.DateTime;

        class MyClass
        {
        }
    }

    与:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    using System;

    namespace MyNamespace
    {
        using MyAlias = DateTime;

        class MyClass
        {
        }
    }

    这可以特别-如果你有一个长期缠绕的别名,如下面的(这是我发现的问题):

    1
    using MyAlias = Tuple<Expression<Func<DateTime, object>>, Expression<Func<TimeSpan, object>>>;

    在一个using声明命名空间,它突然变成:

    1
    using MyAlias = System.Tuple<System.Linq.Expressions.Expression<System.Func<System.DateTime, object>>, System.Linq.Expressions.Expression<System.Func<System.TimeSpan, object>>>;

    不漂亮。


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

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

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

    假定代码文件foo.cs:

    1
    2
    3
    4
    namespace Outer.Inner
    {
        class Foo { }
    }

    Bar.cs:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    namespace Outer
    {
        using Outer.Inner;

        class Bar
        {
            public Foo foo;
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    namespace Outer
    {
        using Inner;

        class Bar
        {
            public Foo foo;
        }
    }


    另一个我不相信被其他答案所覆盖的微妙之处是当你有一个同名的类和名称空间时。

    当您在名称空间中进行导入时,它将找到类。如果导入在命名空间之外,则将忽略导入,并且类和命名空间必须完全限定。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //file1.cs
    namespace Foo
    {
        class Foo
        {
        }
    }

    //file2.cs
    namespace ConsoleApp3
    {
        using Foo;
        class Program
        {
            static void Main(string[] args)
            {
                //This will allow you to use the class
                Foo test = new Foo();
            }
        }
    }

    //file2.cs
    using Foo; //Unused and redundant    
    namespace Bar
    {
        class Bar
        {
            Bar()
            {
                Foo.Foo test = new Foo.Foo();
                Foo test = new Foo(); //will give you an error that a namespace is being used like a class.
            }
        }
    }

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

    通过在项目文件的根目录中添加stylecop.json文件,可以调整stylecop来检查名称空间之外的using指令,方法如下:

    1
    2
    3
    4
    5
    6
    7
    {
     "$schema":"https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
       "orderingRules": {
         "usingDirectivesPlacement":"outsideNamespace"
        }
      }
    }

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


    如果在源解决方案中使用的那些默认的引用(即"引用")应该在名称空间之外,而那些"新添加的引用"是一个好的实践,那就是应该将其放在名称空间内。这是为了区分添加了哪些引用。