“using” 指令应该在名称空间之内还是之外?

我一直在通过某些 C#代码运行StyleCop ,并且一直在报告我的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然后再using命名空间之外的指令查看它们,因此它将找到Outer.Math而不是System.Math 。不幸的是(或者幸运的是?), Outer.Math没有PI成员,因此Outer.Math现在已损坏。

如果将using放在名称空间声明中,则情况会发生变化,如下所示:

// 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 ,会发生什么。在这种情况下,无论using在何处,在Outer.Math中添加Outer.Math破坏Outer.Math 。这意味着编译器在查看任何using指令之前先搜索最里面的命名空间。

这个线程已经有了一些不错的答案,但是我觉得我可以通过这个额外的答案来详细介绍一下。

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

namespace MyCorp.TheProduct.SomeModule.Utilities
{
    ...
}

完全等同于:

namespace MyCorp
{
    namespace TheProduct
    {
        namespace SomeModule
        {
            namespace Utilities
            {
                ...
            }
        }
    }
}

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

解决隐含类型的规则可以大致这样表示: 首先在最内层的 “范围” 中查找匹配项,如果没有找到匹配项,则进入下一个范围并在那里搜索,依此类推 ,直到找到匹配项。如果在某种程度上找到多个匹配项,并且其中一种类型来自当前程序集,则选择该一种并发出编译器警告。否则,放弃(编译时错误)。

现在,让我们在两个主要约定的具体示例中明确说明其含义。

(1)外部使用:

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类型是什么,搜索按以下顺序进行:

  1. C嵌套类型(包括继承的嵌套类型)
  2. 在当前名称空间MyCorp.TheProduct.SomeModule.Utilities
  3. 命名空间MyCorp.TheProduct.SomeModule类型
  4. MyCorp.TheProduct类型
  5. MyCorp类型
  6. 输入名称空间(全局名称空间)
  7. SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty

另一个约定:

(2)内部使用:

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 ” 类型:

  1. C嵌套类型(包括继承的嵌套类型)
  2. 在当前名称空间MyCorp.TheProduct.SomeModule.Utilities
  3. SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProductMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty
  4. 命名空间MyCorp.TheProduct.SomeModule类型
  5. MyCorp类型
  6. 输入名称空间(全局名称空间)

(请注意, MyCorp.TheProduct是 “3.” 的一部分,因此在 “4.” 和 “5.” 之间不需要。)

结束语

无论将 usings 放在名称空间声明的内部还是外部,总有可能有人后来向具有较高优先级的名称空间之一添加了具有相同名称的新类型。

另外,如果嵌套名称空间的名称与类型的名称相同,则可能导致问题。

将使用从一个位置移动到另一个位置始终很危险,因为搜索层次结构会发生变化,并且可能会找到另一种类型。因此,选择一种约定并坚持下去,这样您就不必再搬家了。

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

在外部使用的一个(微小)优点是,您可以然后对全局属性使用 using 指令,例如,使用[assembly: ComVisible(false)]代替[assembly: System.Runtime.InteropServices.ComVisible(false)]

将其放在名称空间中会使声明在文件的该名称空间中处于本地状态(如果文件中有多个名称空间),但是如果每个文件只有一个名称空间,则它们在外部还是外部都不会有太大的区别。在名称空间中。

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

根据Hanselman - 使用指令和程序集加载...以及其他此类文章,技术上没有区别。

我的偏好是将它们放在名称空间之外。

根据 StyleCop 文档:

SA1200:在名称空间中使用 DirectivesMustBePlacedWith

原因 AC#using 指令放置在名称空间元素之外。

规则说明当将 using 指令或 using-alias 指令放置在名称空间元素之外时,会发生违反此规则的情况,除非文件不包含任何名称空间元素。

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

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");的行中发现以下编译器错误时失败Guid g = new Guid("hello");

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

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

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

将 using-alias 指令放置在名称空间之外是一种不好的做法,因为在这种情况下(实际上并不知道实际使用的是哪个版本),这可能导致混乱。这可能会导致可能难以诊断的错误。

在名称空间元素中放置 using-alias 指令可以消除这种情况,使之成为错误的来源。

  1. 多个命名空间

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

重要的是要注意,当使用放置在名称空间之外的指令编写代码时,在将这些指令移至名称空间内时应格外小心,以确保这不会改变代码的语义。如上所述,将 using-alias 指令放置在 namespace 元素中,可使编译器以冲突的方式进行选择,方式是将指令放在命名空间之外时不会发生。

如何解决冲突要解决违反此规则的问题,请在命名空间元素内移动所有 using 指令和 using-alias 指令。

当您希望使用别名时,在名称空间中放置 using 语句存在问题。别名不能从较早的using语句中受益,必须完全限定。

考虑:

namespace MyNamespace
{
    using System;
    using MyAlias = System.DateTime;

    class MyClass
    {
    }
}

与:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

如果您有一个冗长的别名,例如以下内容(这就是我发现问题的方式),则这一点尤其明显:

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

在名称空间内using语句时,它突然变成:

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

不漂亮。

我遇到的一种皱纹(其他答案未涵盖):

假设您具有以下名称空间:

  • 其他
  • 父母,其他

当您在namespace Parent 之外 using Something.Other ,它指的是第一个(Something.Other)。

但是,如果该名称空间声明中使用它,它将引用第二个(Parent.Something.Other)!

有一个简单的解决方案:添加 “ global:: ” 前缀: docs

namespace Parent
{
   using global::Something.Other;
   // etc
}

正如 Jeppe Stig Nielsen 所说 ,该线程已经有了不错的答案,但是我认为这种明显的微妙之处也值得一提。

using在名称空间中指定的指令可以缩短代码长度,因为它们不需要像在外部指定时一样完全合格。

下面的示例起作用,因为类型FooBar都在同一个全局命名空间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;
    }
}

答案中讨论了技术原因,我认为最终涉及的是个人喜好,因为两者之间的差异并不大,而且两者都需要权衡取舍。创建 Visual Studio 的默认模板.cs文件使用using指令的命名空间如外

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

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

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

我不相信其他答案涵盖的另一个微妙之处是,当您拥有一个具有相同名称的类和名称空间时。

当您在名称空间中包含导入时,它将找到该类。如果导入在名称空间之外,则导入将被忽略,并且类和名称空间必须完全合格。

//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.
        }
    }
}