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

ben*_*rce 1975 .net c# namespaces stylecop code-organization

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

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

Cha*_*lie 2050

两者之间实际上存在(微妙的)差异.想象一下,你在File1.cs中有以下代码:

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

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

// File2.cs
namespace Outer
{
    class Math
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在编译器搜索 System在搜索Outer,查找之前搜索,System.Math一切都很好.

有些人认为,Math对于用户定义的类可能是一个坏名称,因为已经有一个System ; 这里要说的就是这样存在差异,并且它会影响您的代码的可维护性.

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

  • 接受的答案是好的,但对我而言,将使用子句_outside_命名空间似乎是一个很好的理由.如果我在命名空间Outer.Inner中,我希望它使用Outer.Inner中的Math类而不是System.Math. (133认同)
  • 我认为这不是一个糟糕的命名约定,而是改变你的使用地点.在您的解决方案中不应该有一个名为Math的类 (28认同)
  • 这是比使用Mark的多命名空间在一个文件参数中本地使用语句更好的理由.特别是正确的编译可以并且会抱怨命名冲突(请参阅StyleCop文档以获取此规则(例如,由Jared发布)). (26认同)
  • 很好的答案,但在我看来,我只想在本地使用非框架使用语句,并使用语句全局保持框架.任何人都有进一步解释为什么我应该完全改变我的偏好 这也是从哪里来的,VS2008中的模板在命名空间之外使用? (13认同)
  • 我同意这一点.接受的答案是正确的,因为它在技术上描述了差异.但是,一个或另一个类需要一个显式的标注.我非常希望将"Math"解析为我自己的本地类,而"System.Math"指的是外部类 - 即使在Outer.Math存在之前System.Math被用作"Math".是的,修复许多预先存在的引用是更多的工作,但这也可能是一个提示,可能Outer.Math应该有一个不同的名称! (7认同)
  • @surfmuggle,如果我没记错的话,我相信您的链接专门指没有主要方法的控制台应用程序。 (6认同)
  • 很明显,如果`using`s在内部或外部,它会产生语义差异.但目前尚不清楚可维护性是否以某种方式更高.这取决于您是否认为具有冲突名称的类型将在您的命名空间中引入(或从`System`命名空间中删除),或者您认为冲突类型将在`System`命名空间中引入(或从命名空间中删除).有关搜索命名空间顺序的明确列表,请参阅我的新答案. (4认同)
  • [基础知识 - 程序结构](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-struct/top-level-statements#using-directives) [标签:c#-9.0] ] [tag:.net-6.0] `using 指令必须位于文件的前面`。有谁知道这对于上面的例子意味着什么? (3认同)
  • @surfmuggle 这是专门指顶级声明的。当使用顶级语句时,命名空间必须位于文件中所有其他顶级语句之后,当然,您也可以在命名空间内添加 using 指令。 (2认同)

Jep*_*sen 430

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

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

namespace MyCorp.TheProduct.SomeModule.Utilities
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

完全等同于:

namespace MyCorp
{
    namespace TheProduct
    {
        namespace SomeModule
        {
            namespace Utilities
            {
                ...
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你愿意,你可以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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,为了找出什么类型Ambiguous,搜索顺序如下:

  1. 内嵌的嵌套类型C(包括继承的嵌套类型)
  2. 当前命名空间中的类型 MyCorp.TheProduct.SomeModule.Utilities
  3. 命名空间中的类型 MyCorp.TheProduct.SomeModule
  4. 类型 MyCorp.TheProduct
  5. 类型 MyCorp
  6. null命名空间中的类型(全局命名空间)
  7. 在类型System,System.Collections.Generic,System.Linq,MyCorp.TheProduct.OtherModule,MyCorp.TheProduct.OtherModule.Integration,和ThirdParty

另一个惯例:

(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 内嵌的嵌套类型C(包括继承的嵌套类型)
  2. 当前命名空间中的类型 MyCorp.TheProduct.SomeModule.Utilities
  3. 在类型System,System.Collections.Generic,System.Linq,MyCorp.TheProduct,MyCorp.TheProduct.OtherModule,MyCorp.TheProduct.OtherModule.Integration,和ThirdParty
  4. 命名空间中的类型 MyCorp.TheProduct.SomeModule
  5. 类型 MyCorp
  6. null命名空间中的类型(全局命名空间)

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

结束语

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

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

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

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

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

  • 这是最好的解释,因为它强调了"使用"语句的位置是开发人员的慎重决定.在任何情况下都不应该在不理解其含义的情况下不小心改变"使用"语句的位置.因此,StyleCop规则只是愚蠢. (39认同)
  • 刚刚遇到一个情况,我继承的类库在 global/null 命名空间中有一些类。其中一个类名与我正在使用的命名空间内的类名相同。“using”位于名称空间定义之外。我不明白为什么它总是会采用全局类的定义。偶然的机会,将命名空间放在外面 - 它就获取了我需要的类。你的回答解释了原因。当命名空间在外面时,null/globals 会最后被选取。对我来说,这是正确/预期的行为。 (4认同)
  • @PaulEvans你的案例也是一个例子,为什么人们不应该在全局命名空间中放置任何东西,尤其是任何公共的东西。您使用的库违反了这一点。 (2认同)

Mar*_*ade 190

将它放在命名空间中会使文件的该命名空间的声明是本地的(如果文件中有多个命名空间),但是如果每个文件只有一个命名空间,那么它们是否外出或者它们没有多大区别在命名空间内.

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,我知道.我们在五年前就这个问题得到了接受的答案. (68认同)
  • 没有区别是不正确的; `namespace`块中的`using`指令可以引用基于封闭`namespace`块的相对命名空间. (9认同)
  • 命名空间提供逻辑分离,而不是物理(文件)分离。 (2认同)

Qui*_*son 61

根据Hanselman - 使用指令和装配......以及其他此类文章,技术上没有区别.

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

  • 请包含链接摘要.**当**链接被破坏时(因为它会*发生,给定足够的时间),突然一个32个upvotes的答案只值得`我的风格是把它们放在命名空间之外. - 几乎没有答案. (79认同)
  • 这里的说法完全是错误的......存在技术差异,你自己的引用也是如此......事实上,这就是它的全部意义所在.请删除这个错误的答案......有更好,更准确的答案. (8认同)
  • @Chris M:呃......答案中张贴的链接表明in in out有_no_的好处,实际上是一个例子,伪造了你发布的链接中的声明... (2认同)
  • 是的,我没有完全阅读该帖子,但在MVP说它是正确的时候买了.一个人反驳它,解释它并向下显示他的代码......"在任何一种情况下,C#编译器生成的IL都是相同的.事实上,C#编译器生成的每个using指令都没有对应的内容.使用指令纯粹是一个C#ism,它们对.NET本身没有任何意义.(对于使用语句不正确,但这些是完全不同的.)"http://groups.google.com/group/wpf-disciples/msg/781738deb0a15c46 (2认同)

Jar*_*rak 48

根据StyleCop文档:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

原因AC#using指令放在命名空间元素之外.

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

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

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,以下代码不会导致违反此规则:

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

    public class Program
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码将干净地编译,没有任何编译器错误.但是,尚不清楚正在分配哪种版本的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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

代码在包含的行上找到的以下编译器错误失败 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. 多个命名空间

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

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

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

  • 实际上,它也是一个StyleCop规则!SA1402:AC#文档在根级别只能包含一个类,除非所有类都是部分类并且属于同一类型.展示一个规则,打破另一个只是滴错了酱. (24认同)
  • 倾向于成为第一个从StyleCop角度实际覆盖它的答案.就个人而言,我喜欢`在名称空间外使用`的视觉感受.内在`使用`看起来对我来说太丑了.:) (6认同)
  • @Jared - 正如我在回答中指出的,我喜欢的解决方法/解决方案是每个文件只有一个类。我认为这是一个相当普遍的惯例。 (2认同)
  • 最后一个很好的答案问题.benPearce的评论无关紧要......这与文件中的类数无关. (2认同)

Neo*_*Neo 34

当您希望使用别名时,在命名空间内放置使用语句会出现问题.别名不会从早期的using陈述中受益,必须完全合格.

考虑:

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

    class MyClass
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

与:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您有一个冗长的别名,例如以下(这是我发现问题的方式),这可能会特别明显:

using MyAlias = Tuple<Expression<Func<DateTime, object>>, Expression<Func<TimeSpan, object>>>;
Run Code Online (Sandbox Code Playgroud)

使用using命名空间内的语句,它突然变为:

using MyAlias = System.Tuple<System.Linq.Expressions.Expression<System.Func<System.DateTime, object>>, System.Linq.Expressions.Expression<System.Func<System.TimeSpan, object>>>;
Run Code Online (Sandbox Code Playgroud)

不漂亮.

  • 你的“类”需要一个名称(标识符)。正如您所指出的,您不能在类中使用“using”指令。它必须位于命名空间级别,例如在最外面的“命名空间”之外,或者就在最里面的“命名空间”内部(但不在类/接口/等内部)。 (2认同)

Han*_*ing 9

我遇到的一个皱纹(其他答案中没有提到):

假设您有这些命名空间:

  • 其他
  • 父母.某事.其他

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

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

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

namespace Parent
{
   using global::Something.Other;
   // etc
}
Run Code Online (Sandbox Code Playgroud)


Bis*_*its 6

正如 Jeppe Stig Nielsen所说,这个帖子已经有了很好的答案,但我认为这个相当明显的微妙之处也值得一提。

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

以下示例有效,因为类型FooBar都在同一个全局命名空间Outer.

假设代码文件Foo.cs

namespace Outer.Inner
{
    class Foo { }
}
Run Code Online (Sandbox Code Playgroud)

Bar.cs

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 确实,您“可以省略外部命名空间”,但这并不意味着您应该这样做。对我来说,这是关于为什么 using 指令(@Neo 的答案中的别名除外)应该在命名空间之外的另一个论点,以强制使用完全限定的命名空间名称。 (10认同)

sot*_*otn 6

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

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

{
  "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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


Mik*_*kis 6

尚未提及:

using将指令放置名称空间声明中是众所周知的最佳编程实践的应用,即在尽可能小的范围内声明所有内容

如果最佳编程实践是您的第二天性,那么您会自动执行类似的操作。

这可能是将您的 using 指令放入名称空间声明中的最佳理由,无论其他地方提到的(边界)技术(边界)优点如何;就这么简单。


已经提到但也许更好地说明:

using将指令放置命名空间内可以避免不必要的重复,并使我们的声明更加简洁。

这是不必要的简洁:

using Com.Acme.Products.Traps.RoadRunnerTraps;
namespace Com.Acme.Products.Traps {
Run Code Online (Sandbox Code Playgroud)

这很甜蜜,也很切中要点:

namespace Com.Acme.Products.Traps { 
using RoadRunnerTraps;
Run Code Online (Sandbox Code Playgroud)


Ben*_*ner 5

我认为其他答案没有涵盖的另一个微妙之处是当您有一个同名的类和命名空间时。

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

//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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)