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

C# “using”指令应该在命名空间内部还是外部?,c#,.net,namespaces,stylecop,code-organization,C#,.net,Namespaces,Stylecop,Code Organization,我已经运行了一些C代码,它一直报告我的using指令应该在名称空间中 using ThisNamespace.IsImported.InAllNamespaces.Here; namespace Namespace1 { using ThisNamespace.IsImported.InNamespace1.AndNamespace2; namespace Namespace2 { using ThisNamespace.IsImported.InJustN

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

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

如果文件中有多个名称空间,则将其放在名称空间内部会使声明位于该文件的名称空间的本地,但如果每个文件只有一个名称空间,则它们位于名称空间的外部或内部

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
根据和其他类似的文章,在技术上没有区别


我倾向于将它们放在名称空间之外。

这两者之间实际上有细微的区别。假设您在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.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.Math,一切正常

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


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

根据StyleCop文档:

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

原因 C 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 Guidhello的行上

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

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

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

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

在namespace元素中使用alias指令可以消除这一缺陷

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

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
如何纠正违规行为
若要修复违反此规则的情况,请在命名空间元素中移动所有using指令和using alias指令。

如果要使用别名,请在命名空间中放置using语句。别名不能从前面的using语句中获益,必须完全限定

//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.
        }
    }
}
考虑:

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

不太好看。

这篇文章已经有了一些很好的答案,但我觉得我可以用这个额外的答案提供更多的细节

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

namespace MyCorp.TheProduct.SomeModule.Utilities
{
    ...
}
完全等同于:

namespace MyCorp
{
    namespace TheProduct
    {
        namespace SomeModule
        {
            namespace Utilities
            {
                ...
            }
        }
    }
}
如果您愿意,您可以将using指令放在所有这些级别上。当然,我们只希望在一个地方使用,但根据语言,这是合法的

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

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

1在外部使用时:

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

C中的嵌套类型,包括继承的嵌套类型 在当前命名空间MyCorp.TheProduct.SomeModule.Utilities中键入 在System、System.Collections.Generic、System.Linq、MyCorp.TheProduct、MyCorp.TheProduct.OtherModule、MyCorp.TheProduct.OtherModule.Integration和第三方中键入 命名空间MyCorp.TheProduct.SomeModule中的类型 MyCorp的类型 在全局命名空间的空命名空间中键入 请注意,MyCorp.TheProduct是3的一部分。因此,在4之间不需要。和5

结束语

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

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

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

默认情况下,VisualStudio的模板将using放在名称空间之外,例如,如果在新文件中生成一个新类


在外部使用的一个微小优势是,您可以利用全局属性的using指令,例如[assembly:ComVisiblefalse],而不是[assembly:System.Runtime.InteropServices.ComVisiblefalse].

如果源解决方案中使用的默认引用应位于名称空间之外,而新添加的引用则应位于名称空间之内,这是一种更好的做法。这是为了区分要添加的引用。

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

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

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

假定代码文件Foo.cs:

和Bar.cs:

这可能会在using指令中忽略外部名称空间,简称:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

答案中讨论了技术方面的原因,我认为最终会涉及到个人偏好,因为差异并没有那么大,而且两者都有权衡。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"
    }
  }
}

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

我认为其他答案中未涉及的另一个微妙之处是,当您拥有同名的类和命名空间时

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

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

我遇到的一条皱纹没有在其他答案中涵盖:

假设你有 se名称空间:

其他的 父母,某物,其他 当您在命名空间父级之外使用Something.Other时,它引用第一个Something.Other

但是,如果在名称空间声明中使用它,它将引用第二个父对象。Something.Other

有一个简单的解决方案:添加全局::前缀:


在引用微软的内部指导方针时,请记住这些指导方针是由一位可能拥有不到十年编码经验的人编写的。换言之,他们可能只基于个人偏好。特别是在像C这样的新事物中

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

它提供了本地模块和“其他”模块之间的视觉区别,这意味着其他所有模块。 它确定了优先应用于全局指令的本地指令的范围。 后一个原因很重要。这意味着很难引入一个不明确的引用问题,而这个问题可能是由一个与重构代码相比意义不大的更改引起的。也就是说,您将一个方法从一个文件移动到另一个文件,突然出现了以前没有的bug。通俗地说,一个“海森堡”——历史上难以追踪的恶魔


作为一个更一般的规则,一个好的遵循是这样的。如果您看到某种语言固有的东西似乎是无用的选项,那么假设它不是。事实上,越难理解这个选项为什么存在,你就应该认为它越重要。对两种选择之间的具体差异进行研究,然后仔细思考其含义。对于一个晦涩难懂的问题,你通常会发现一个非常有洞察力和聪明的解决方案,语言设计师专门为你的生活提供了这个解决方案。适当地感激并利用它。

@Jared-正如我在回答中所指出的,我更喜欢的解决方法/解决方案是每个文件只有一个类。我认为这是一个相当普遍的惯例。事实上,这也是一个StyleCop规则!SA1402:C文档在根级别只能包含一个类,除非所有类都是部分类且类型相同。通过打破另一条规则来展示一条规则,只是用错误的酱汁滴了一滴。投票赞成成为第一个从StyleCop的角度真正涵盖它的答案。就我个人而言,我喜欢在名称空间之外使用的视觉感受。内心的使用在我看来是如此丑陋最后是对这个问题的一个很好的回答。benPearce的评论与此无关。。。这与文件中的类数无关。@Chris M:嗯。。。答案中的链接表明in与out没有任何好处,实际上展示了一个例子,证明了你发布的链接中的声明是错误的……是的,我没有完全阅读该线程,但当MVP说它是正确的时候,我就接受了。一个家伙反驳了它,解释了它,并进一步展示了他的代码。。。在这两种情况下,C编译器生成的IL都是相同的。事实上,C编译器完全不生成与每个using指令对应的任何内容。使用指令纯粹是一种逻辑推理,它们对.NET本身没有任何意义。不适用于使用语句,但它们是完全不同的。请包括该链接的摘要。当链接因为即将发生而断开时,如果有足够的时间,突然间,一个32票的答案只值得一次。我的风格是将它们放在名称空间之外几乎没有答案。这里的说法完全是错误的。。。这是一个技术上的差异,你自己的引文也这么说。。。事实上,这就是一切。请删除这个错误的答案。。。有更好、更准确的方法。有时,使用方法会有所不同:仅供参考,除了每个文件有多个类的问题外,还有其他含义,因此如果您不熟悉这个问题,请继续阅读。@user-12506-这在需要一定程度的代码一致性的中大型开发团队中不太适用。如前所述,如果您不了解不同的布局,您可能会发现边缘案例并不像您预期的那样工作;他们正在使用指令。另一方面,using语句是一种语言结构,与方法体中的其他语句一起出现。例如,using var e=s.GetEnumerator{/*…*/}是一种与var e=s.GetEnumerato大致相同的语句
{
  "$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.
        }
    }
}
namespace Parent
{
   using global::Something.Other;
   // etc
}