'using'指令应该在命名空间的内部还是外部?

时间:2008-09-24 03:49:50

标签: c# .net namespaces stylecop code-organization

我一直在运行StyleCop一些C#代码,并且它一直报告我的using指令应该在命名空间内。

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

12 个答案:

答案 0 :(得分:1976)

两者之间实际上存在(微妙的)差异。想象一下,你在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成员,所以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,会发生什么情况也很有意思。在这种情况下,无论Outer.Math在哪里,在File2中添加using都会破坏File1。这意味着编译器在查看任何using指令之前搜索最里面的封闭命名空间。

答案 1 :(得分:397)

这个帖子已经有了一些很好的答案,但我觉得我可以通过这个额外的答案带来更多细节。

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

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

完全等同于:

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

如果您愿意,可以在所有这些级别上添加using指令。 (当然,我们希望只在一个地方设置using,但根据语言,它是合法的。)

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

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

(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. 中的类型
  5. MyCorp.TheProduct
  6. 中的类型
  7. MyCorp
  8. 中的类型
  9. null 命名空间(全局命名空间)中的类型
  10. SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty
  11. 中的类型

    另一个惯例:

    (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. 中的类型
    6. MyCorp
    7. 中的类型
    8. null 命名空间(全局命名空间)中的类型
    9. (请注意MyCorp.TheProduct是“3.”的一部分,因此在“4.”和“5”之间不需要。)

      结束语

      无论你是否在命名空间声明的内部或外部放置了使用,总有可能以后有人将具有相同名称的新类型添加到具有更高优先级的命名空间之一。

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

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

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

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

答案 2 :(得分:188)

将它放在命名空间中会使该文件的该命名空间的声明是本地的(如果文件中有多个命名空间),但是如果每个文件只有一个命名空间,那么它们是否有很大区别走到命名空间的外面或里面。

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

答案 3 :(得分:61)

根据Hanselman - Using Directive and Assembly Loading...和其他此类文章,技术上没有区别。

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

答案 4 :(得分:47)

根据StyleCop文档:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

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

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

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

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'冲突的定义

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

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

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

在namespace元素中放置using-alias指令会将其作为bug的来源消除。

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

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

    如何修复违规行为 要修复违反此规则的行为,请在namespace元素中移动所有using指令和using-alias指令。

答案 5 :(得分:32)

当您希望使用别名时,在命名空间中放置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>>>;

不漂亮。

答案 6 :(得分:3)

作为Jeppe Stig Nielsen said,这个主题已经有了很好的答案,但我认为这个相当明显的微妙之处也值得一提。

命名空间内指定的

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;
    }
}

答案 7 :(得分:3)

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

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

  • 其他东西
  • Parent.Something.Other

Error connecting to the service protocol: HttpException: Connection closed before full header was received, uri = http://127.0.0.1:51195/1vmwn2gEVK8=/ws 中使用using Something.Other外部时,它指的是第一个(Something.Other)。

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

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

global::

答案 8 :(得分:3)

在引用Microsoft内部指南时,请记住,这些指南是由可能具有不到十年编码经验的人编写的。换句话说,他们可能只是基于个人喜好而已。尤其是在像C#这样的新事物中。

通常,应将外部using指令(例如,系统名称空间和Microsoft名称空间)放置在namespace指令的外部之外。这些默认值应除非另有说明在所有情况下都适用。这应该包括不属于当前项目的您自己组织的任何内部库,或引用同一项目中其他主要名称空间的using指令。引用当前项目和名称空间中其他模块的所有using伪指令都应放在内部namespace伪指令。这具有两个特定功能:

  • 它在本地模块和“其他”模块之间提供了视觉上的区别,意味着其他一切。
  • 它将范围内的本地指令应用于优先应用于全局指令。

后一个原因很重要。这意味着要引入一个模棱两可的参考问题比较困难,而该问题可以通过仅比重构代码重要的更改来引入。就是说,您将一种方法从一个文件移动到另一个文件,突然出现了以前没有的错误。通俗地说,是一个“ heisenbug”-历史上很难追踪。

作为更一般的规则,这是一个值得遵循的规则。如果您看到某种语言固有的东西似乎是无用的选择,请假定它不是。实际上,越难理解该选项的存在原因,就应该假设它越重要。研究两个选项之间的特定差异,然后仔细思考其含义。通常,您会找到语言设计师为解决一个晦涩问题而提供的极富见解和聪明的解决方案,特别是为了使您的生活更轻松。适当地感恩并充分利用它。

答案 9 :(得分:1)

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

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

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

答案 10 :(得分:0)

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

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

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

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

答案 11 :(得分:-8)

如果那些默认使用ie&#34; 引用&#34;这是更好的做法。在源代码解决方案中使用的应该在命名空间之外,而那些&#34;新添加的引用&#34; 是一个很好的做法,你应该将它放在命名空间中。这是为了区分正在添加的引用。