题 “使用”指令应该在命名空间内部还是外部?


我一直在跑 了StyleCop 一些C#代码,它不断报告我的 using 指令应该在命名空间内。

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


1741
2017-09-24 03:49


起源


有时它会使你使用的地方有所不同: stackoverflow.com/questions/292535/linq-to-sql-designer-bug - gius
仅供参考,除了每个文件的多个类的问题之外还有其他含义,所以如果您对这个问题不熟悉,请继续阅读。 - Charlie
@ user-12506 - 这在中型到大型开发团队中效果不佳,需要一定程度的代码一致性。如前所述,如果您不了解不同的布局,您可能会发现无法按预期工作的边缘情况。 - benPearce
术语:那些不是 using  声明;他们是 using  指令。一个 using 另一方面,语句是与方法体等内部的其他语句一起出现的语言结构。例如, using (var e = s.GetEnumerator()) { /* ... */ } 是一个与...松散相同的陈述 var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }。 - Jeppe Stig Nielsen
如果没有人提到这一点,实际上微软也建议推杆 using 里面的陈述 namespace 声明,在他们的 内部编码指南 - user1451111


答案:


两者之间实际上存在(微妙的)差异。想象一下,你在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中,无论在哪里,都会破坏File1 using 去。这意味着编译器在查看任何内部之前搜索最里面的封闭命名空间 using 指示。


1844
2017-09-30 02:33



这是比使用Mark的多命名空间在一个文件参数中本地使用语句更好的理由。特别是正确的编译可以并且会抱怨命名冲突(请参阅StyleCop文档以获取此规则(例如,由Jared发布))。 - David Schmitt
接受的答案是好的,但对我来说,似乎是使用使用条款的一个很好的理由 外 命名空间。如果我在命名空间Outer.Inner中,我希望它使用Outer.Inner中的Math类而不是System.Math。 - Frank Wallis
我同意这一点。接受的答案是正确的,因为它在技术上描述了差异。但是,一个或另一个类需要一个显式的标注。我非常希望将“Math”解析为我自己的本地类,而“System.Math”指的是外部类 - 即使在Outer.Math存在之前System.Math被用作“Math”。是的,修复许多预先存在的引用是更多的工作,但这也可能是一个提示,也许Outer.Math应该有一个不同的名称! - mbmcavoy
很好的答案,但在我看来,我只想在本地使用非框架使用语句,并使用语句全局保持框架。任何人都有进一步解释为什么我应该完全改变我的偏好这也是从哪里来的,VS2008中的模板在命名空间之外使用? - Thymine
我认为这更像是一个糟糕的命名约定,而不是改变你的使用地点。在您的解决方案中不应该有一个名为Math的类 - jDeveloper


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

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

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

完全等同于:

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

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

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

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

(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. 中的类型 空值 namespace(全局命名空间)
  7. 类型 SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.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;
    }
}

现在,搜索类型 Ambiguous 按顺序排列:

  1. 嵌套类型在里面 C (包括继承的嵌套类型)
  2. 当前命名空间中的类型 MyCorp.TheProduct.SomeModule.Utilities
  3. 类型 SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProductMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.Integration,和 ThirdParty
  4. 命名空间中的类型 MyCorp.TheProduct.SomeModule
  5. 类型 MyCorp
  6. 中的类型 空值 namespace(全局命名空间)

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

结束语

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

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

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

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

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


346
2018-04-18 21:00



谢谢,这是一个比接受的答案更好的解释。 - thor_hayek
这是最好的解释,因为它强调了“使用”语句的位置是开发人员故意做出的决定。在任何情况下都不应该在不理解其含义的情况下不小心改变“使用”语句的位置。因此,StyleCop规则只是愚蠢。 - ZunTzu


将它放在命名空间中会使文件的该命名空间的声明本地化(如果文件中有多个命名空间),但是如果每个文件只有一个命名空间,那么无论它们是在外面还是在外面都没有多大区别在命名空间内。

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

178
2017-09-24 03:52



名称空间提供逻辑分隔,而不是物理(文件)分隔。 - Jowen
没有区别是不正确的; using 指令内 namespace 块可以基于封闭来引用相对名称空间 namespace 块。 - O. R. Mapper
是的,我知道。我们在五年前就这个问题得到了接受的答案。 - Mark Cidade


根据 汉塞尔曼 - 使用指令和装配加载...... 和其他这样的文章在技术上没有区别。

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


56
2017-09-24 03:53



@Chris M:呃......答案中贴出的链接表明有 没有 有利于in和out,实际上显示了一个例子,伪造了你发布的链接中的声明... - johnny
是的,我没有完全阅读该帖子,但在MVP说它是正确的时候买了。一个人反驳它,解释它并向下显示他的代码......“在任何一种情况下,C#编译器生成的IL都是相同的。事实上,C#编译器生成的每个using指令都没有对应的任何东西。使用指令纯粹是一个C#ism,它们对.NET本身没有任何意义。(对于使用语句不正确,但这些是完全不同的。)“ groups.google.com/group/wpf-disciples/msg/781738deb0a15c46 - Chris McKee
请包含链接摘要。 什么时候 链接坏了(因为它 将 发生,如果有足够的时间),突然得到32个upvotes的答案是值得的 My style is to put them outside the namespaces.  - 几乎没有答案。 - ANeves
这里的说法是完全错误的......存在技术差异,你自己的引用也是如此......实际上,这就是它的全部意义所在。请删除这个错误的答案......有更好,更准确的答案。 - Jim Balter


根据StyleCop文档:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

原因 C#using指令放在namespace元素之外。

规则说明 如果将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. 多个命名空间

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

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

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


45
2017-09-14 15:17



@Jared - 正如我在回答中提到的,我的首选解决方法/解决方案是每个文件只有一个类。我认为这是一个相当普遍的惯例。 - benPearce
实际上,它也是一个StyleCop规则! SA1402:C#文档在根级别只能包含一个类,除非所有类都是部分类并且属于同一类型。展示一个规则,打破另一个只是滴错了酱。 - Task
倾向于成为第一个从StyleCop角度实际覆盖它的答案。我个人喜欢视觉感受 using在命名空间之外。内 using我看起来很难看。 :) - nawfal
最后这个问题的答案很好。 benPearce的评论无关紧要......这与文件中的类数无关。 - Jim Balter


当您希望使用别名时,在命名空间内放置使用语句会出现问题。别名不会从早期受益 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>>>;

不漂亮。


29
2017-10-10 18:47



你的 class 需要一个名字(标识符)。你不能拥有 using 你指出的类中的指令。它必须位于命名空间级别,例如在最外层 namespace,或者只是在最里面 namespace (但不在类/接口/ etc中)。 - Jeppe Stig Nielsen
@JeppeStigNielsen谢谢。我放错了地方 using 指令错误地。我把它编辑成了我的预期。谢谢你指出。但是,推理仍然是相同的。 - Neo


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

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

以下示例适用,因为类型 Foo 和 Bar 都在同一个全局命名空间中, 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;
    }
}

2
2017-09-17 10:32



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


技术原因在答案中进行了讨论,我认为最终涉及个人偏好,因为差异并非如此  并且两者都存在权衡。 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"
    }
  }
}

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


0
2018-06-03 12:38