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


2060

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

是否有技术上的理由将using指令放在名称空间的内部而不是外部?


4
有时,放置使用的地方会有所不同:stackoverflow.com/questions/292535/linq-to-sql-designer-bug
gius

82
仅作为参考,其含义不只是每个文件多个类的问题,因此,如果您不熟悉此问题,请继续阅读。
查理

3
@ user-12506-在需要一定程度的代码一致性的中型到大型开发团队中,这不能很好地工作。如前所述,如果您不了解不同的布局,可能会发现边缘盒无法按预期工作。
benPearce 2014年

34
术语:这些不是using 陈述;它们是using 指令。甲using语句,在另一方面,是与方法体等。作为示例内其它语句沿发生语言结构,using (var e = s.GetEnumerator()) { /* ... */ }是松散相同的声明var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }
杰普·斯蒂格·尼尔森

1
如果任何人都没有提到过,实际上,Microsoft也建议您在其内部编码usingnamespace
准则中将

Answers:


2132

两者之间实际上存在(细微)差异。假设您在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对于用户定义的类来说可能是个坏名字,因为已经有in了System;这里要说的就是这样存在差异,并且它会影响您的代码的可维护性。

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


28
与Mark的“文件中的多个命名空间”参数相比,这是在本地放置语句更好的理由。特别是正弦的,编译器会并且会抱怨命名冲突(有关此规则,请参见StyleCop文档(例如,由Jared发表))。
David Schmitt,2009年

147
可接受的答案是好的,但是对我来说,将using子句放在命名空间之外似乎是一个很好的理由。如果我在命名空间Outer.Inner中,则希望它使用Outer.Inner中的Math类,而不是System.Math。
Frank Wallis

7
我也同意这一点。可接受的答案是正确的,因为它从技术上描述了差异。但是,一个或另一个类将需要显式标注。我非常愿意把“ Math”解析为我自己的本地类,并且“ System.Math”是指外部类-即使System.Math在Outer.Math存在之前被用作“ Math”。是的,修复许多现有参考文献需要付出更多的努力,但这也可能暗示Outer.Math应该使用不同的名称!
mbmcavoy 2012年

13
很好的答案,但是在我看来,我只想在本地放置使用语句的非框架,并在全局使用语句的框架。有人进一步解释了为什么我应该完全改变我的偏好吗?另外,这是从哪里来的,VS2008中的模板放在命名空间之外使用?
胸腺嘧啶

30
我认为这更多是一种不好的命名约定,而不是更改使用位置。您的解决方案中不应有一个名为Math的类
jDeveloper 2012年

454

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

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

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. 输入 MyCorp.TheProduct
  5. 输入 MyCorp
  6. 输入名称空间(全局名称空间)
  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. 输入名称空间(全局名称空间)

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

结束语

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

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

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

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

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


46
这是最好的解释,因为它强调了“ using”语句的位置是开发人员有意决定的事实。在任何情况下,任何人都不应在不理解其含义的情况下不慎更改“使用”语句的位置。因此,StyleCop规则只是愚蠢的。
尊子

194

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

名称空间提供逻辑上的分隔,而不是物理上的(文件)分隔。
Jowen

9
没有区别是不完全正确的。块中的using伪指令namespace可以引用基于封闭namespace块的相对名称空间。
OR Mapper 2014年

70
是的,我知道。我们在五年前确定了该问题的公认答案。
Mark Cidade 2014年

59

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

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


3
@Chris M:呃...答案中张贴的链接表明进与出没有任何好处,实际上显示了一个示例,该示例伪造了您张贴的链接中提出的主张...
johnny

2
是的,我没有完全阅读该主题,但是在MVP表示是正确的时候买了。一个家伙反驳了它,解释了它,并进一步展示了他的代码...“在两种情况下,C#编译器生成的IL都是相同的。实际上,C#编译器完全不生成与每个using指令相对应的内容。using指令纯粹是一个C#ism,它们对.NET本身没有任何意义。(使用语句不是正确的,但是有些不同。)“ groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Chris McKee

84
请提供链接摘要。链接断开时(因为它发生,需要足够的时间),突然只有32个投票的答案才有价值My style is to put them outside the namespaces.-几乎没有答案。
2012年

11
这里的说法完全是错误的……存在技术上的差异,您自己的引用也是如此……实际上,这就是全部。请删除这个错误的答案……还有更好,更准确的答案。
Jim Balter

53

根据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");

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指令。


1
@Jared-正如我在回答中指出的那样,我更喜欢的解决方法/解决方案是每个文件只能有一个类。我认为这是一个相当普遍的约定。
benPearce

24
确实,这也是StyleCop规则!SA1402:AC#文档在根级别只能包含一个类,除非所有类都是部分类并且属于同一类型。通过打破另一条规则来展示一条规则,只是滴上了错误的酱汁。
任务

6
赞扬它是从StyleCop角度实际覆盖它的第一个答案。我个人喜欢usings在命名空间之外的视觉效果。内心using对我来说看起来很丑。:)
nawfal

2
终于很好地回答了这个问题。而且benPearce的评论是无关紧要的……这与文件中的类数无关。
Jim Balter

35

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

不漂亮。


1
class需要一个名称(标识符)。您不能using在类中包含所指示的指令。它必须在名称空间级别上,例如,在最外层的外部namespace,或仅在最内层的内部namespace(但不能在类/接口/等内部)。
Jeppe Stig Nielsen

@JeppeStigNielsen谢谢。我把using指令放错了地方。我已经将其编辑为预期的样子。感谢您指出。不过,推理还是一样的。
2016年

4

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

8
的确,您“可以省略外部名称空间”,但这并不意味着您应该这样做。对我来说,这是另一个争论,为什么使用指令(@Neo回答中的别名除外)应该超出命名空间,以强制使用完全限定的命名空间名称。
基思·罗伯森

4

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

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

  • 其他
  • 父母,其他

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

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

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

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

2

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

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

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

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


2

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

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

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

-8

如果那些是更好的做法 在源解决方案中使用默认值(即“ 引用 ”)的默认值应该在命名空间之外,而那些“新添加引用”的默认值应该是一个好习惯,则应将其放在命名空间中。这是为了区分正在添加的引用。


6
不,实际上这是一个坏主意。您不应基于使用指令是在本地范围还是在全局范围之间使用位置而并非基于新添加的事实。取而代之的是,最好按字母顺序排列它们,但BCL引用除外,该引用应放在最前面。
Abel 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.