“使用”语句应该在命名空间的内部还是外部?
我一直在一些C#代码上运行StyleCop ,它一直在报告我的using
语句应该在命名空间内。
将using
语句放在命名空间之外而不是using
技术原因?
两者之间实际上有一个(微妙的)区别。 想象一下你在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 { } }
编译器在查看名称空间外的那些using
语句之前先searchOuter
,以便findOuter.Math
而不是System.Math
。 不幸的是(或者幸运的是), Outer.Math
没有PI
成员,所以Outer.Math
现在被破坏了。
如果你把你的名字空间声明放在里面,这个改变如下:
// File1b.cs namespace Outer.Inner { using System; class Foo { static void Bar() { double d = Math.PI; } } }
现在编译器在searchOuter
之前searchSystem
,findSystem.Math
,一切正常。
有些人会说Math
可能是一个用户定义的类的坏名字,因为在System
已经有了一个; 这里的重点只是有所不同,而且会影响代码的可维护性。
注意到如果Foo
在命名空间Outer
而不是Outer.Inner
,会发生什么情况也很有趣。 在这种情况下,无论using
的位置如何,在Outer.Math
中添加Outer.Math
中断Outer.Math
。 这意味着编译器在查看任何using
语句之前search最内层的封闭名称空间。
这个线程已经有了一些很好的答案,但我觉得我可以带来更多的细节与这个额外的答案。
首先,请记住一个带有句点的名称空间声明,如:
namespace MyCorp.TheProduct.SomeModule.Utilities { ... }
完全等同于:
namespace MyCorp { namespace TheProduct { namespace SomeModule { namespace Utilities { ... } } } }
如果你想,你可以在所有这些级别上using
指令。 (当然,我们只想在一个地方using
s,但根据这个语言,这是合法的。)
解决这种types的规则可以这样粗略地表述: 首先search最内部的“范围”进行匹配,如果没有发现任何内容,则出现一个级别到下一个范围并在那里search,等等 ,直到find一个匹配。 如果在某个级别find多个匹配项,如果其中一个types来自当前程序集,请select该项并发出编译器警告。 否则,放弃(编译时错误)。
现在让我们来明确一下这两个主要公约的具体例子是什么意思。
(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
是什么types,search顺序如下:
-
C
嵌套types(包括inheritance的嵌套types) - 当前命名空间
MyCorp.TheProduct.SomeModule.Utilities
types - 名称空间中的types
MyCorp.TheProduct.SomeModule
-
MyCorp.TheProduct
types -
MyCorp
types - 空名称空间中的types(全局名称空间)
-
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; } }
现在,按照这个顺序search“ Ambiguous
”types:
-
C
嵌套types(包括inheritance的嵌套types) - 当前命名空间
MyCorp.TheProduct.SomeModule.Utilities
types -
System.Collections.Generic
,System.Linq
,MyCorp.TheProduct
,MyCorp.TheProduct.OtherModule
,MyCorp.TheProduct.OtherModule.Integration
和ThirdParty
- 名称空间中的types
MyCorp.TheProduct.SomeModule
-
MyCorp
types - 空名称空间中的types(全局名称空间)
(请注意, MyCorp.TheProduct
是“3.”的一部分,因此在“4.”和“5.”之间不需要)。
总结发言
不pipe你把用途放在名字空间声明的内部还是外部,总是有可能以后有人用一个具有相同名字的新types给其中一个具有较高优先级的名字空间添加一个新types。
而且,如果一个嵌套的名称空间与一个types名称相同,则会导致问题。
将使用从一个位置移动到另一个位置是非常危险的,因为search层次结构会发生变化,并且可能会find其他types。 因此,select一个惯例并坚持下去,这样你就不必再使用了。
Visual Studio的模板,默认情况下,把用户的命名空间之外 (例如,如果你让VS在新文件中生成一个新的类)。
外部使用的一个(微小的)优点是你可以使用全局属性的using指令,例如[assembly: ComVisible(false)]
而不是[assembly: System.Runtime.InteropServices.ComVisible(false)]
。
把它放到命名空间里面,这个声明是这个文件的命名空间的本地的(如果你在文件中有多个命名空间的话),但是如果你每个文件只有一个命名空间的话,在命名空间内。
using ThisNamespace.IsImported.InAllNamespaces.Here; namespace Namespace1 { using ThisNamespace.IsImported.InNamespace1.AndNamespace2; namespace Namespace2 { using ThisNamespace.IsImported.InJustNamespace2; } } namespace Namespace3 { using ThisNamespace.IsImported.InJustNamespace3; }
根据Hanselman – 使用指令和assembly加载…和其他这样的文章,技术上没有区别。
我的首选是把它们放在命名空间之外。
根据StyleCop文档:
SA1200:UsingDirectivesMustBePlacedWithinNamespace
原因AC#使用指令放置在命名空间元素之外。
规则说明当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 { } }
这段代码将干净地编译,没有任何编译错误。 但是,目前还不清楚Guidtypes的哪个版本正在被分配。 如果将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”冲突的定义
该代码创build一个称为Guid的System.Guidtypes的别名,并创build一个名为Guid的自己的types,并带有一个匹配的构造函数接口。 稍后,代码将创build一个Guidtypes的实例。 为了创build这个实例,编译器必须在Guid的两个不同的定义之间进行select。 当use-alias指令放置在名称空间元素之外时,编译器将select在本地名称空间内定义的Guid的本地定义,并完全忽略名称空间外部定义的using-alias指令。 这不幸的是,在阅读代码时并不明显。
然而,当using-alias指令位于名称空间内时,编译器必须在两个在相同名称空间内定义的不同冲突Guidtypes之间进行select。 这两种types都提供了一个匹配的构造函数。 编译器无法做出决定,所以它会标记编译器错误。
在命名空间之外放置using-alias指令是一个不好的做法,因为在这种情况下会导致混淆,在这种情况下,哪种types的版本实际上并不明显。 这可能会导致一个可能难以诊断的错误。
在命名空间元素中放置使用别名指令可以消除这个错误。
- 多个命名空间
将多个名称空间元素放在一个单独的文件中通常是一个坏主意,但是如果这样做,那么将所有使用指令放在每个名称空间元素中,而不是全局在文件顶部是一个好主意。 这将紧紧地限制名称空间,并且还将有助于避免上述那种行为。
值得注意的是,当使用放置在命名空间之外的指令编写代码时,在命名空间内移动这些命令时要小心,以确保这不会改变代码的语义。 如上所述,在名称空间元素中放置使用别名指令允许编译器以指令放置在名称空间之外时不会发生的冲突types之间进行select。
如何解决冲突为了解决违反这个规则的问题,在命名空间元素中移动所有使用指令和使用别名指令。
当您希望使用别名时,在命名空间中放置使用语句会出现问题。 别名不会从早期的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>>>;
不漂亮。
就像杰普·斯蒂格·尼尔森(Jeppe Stig Nielsen) 所说 ,这个线索已经有了很好的答案,但我认为这个相当明显的微妙之处也值得一提。
在命名空间中using
指令可以缩短代码的长度,因为它们不需要像外部指定的那样完全限定。
下面的例子是因为Foo
和Bar
types都在同一个全局命名空间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; } }
如果在你的源代码解决scheme中使用的“ 引用 ”应该在命名空间之外,而那些是“新增引用”的 默认使用是一个好的做法,那么你应该把它放在命名空间中。 这是为了区分哪些引用被添加。