我一直在跑 了StyleCop 一些C#代码,它不断报告我的 using
指令应该在命名空间内。
是否存在技术原因 using
内部而不是命名空间外的指令?
我一直在跑 了StyleCop 一些C#代码,它不断报告我的 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
{
}
}
编译器搜索 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
指示。
这个帖子已经有了一些很好的答案,但我觉得我可以通过这个额外的答案带来更多细节。
首先,请记住带有句点的名称空间声明,例如:
namespace MyCorp.TheProduct.SomeModule.Utilities
{
...
}
完全等同于:
namespace MyCorp
{
namespace TheProduct
{
namespace SomeModule
{
namespace Utilities
{
...
}
}
}
}
如果你愿意,你可以放 using
所有这些级别的指令。 (当然,我们希望拥有 using
s只在一个地方,但根据语言是合法的。)
解析哪种类型的规则可以宽泛地说明如下: 首先搜索匹配的最内部“范围”,如果找不到任何内容,则将一个级别输出到下一个范围并在那里搜索,依此类推,直到找到匹配。如果在某个级别找到多个匹配项,如果其中一个类型来自当前程序集,则选择该类型并发出编译器警告。否则,放弃(编译时错误)。
现在,让我们在两个主要约定的具体例子中明确这意味着什么。
(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
是,搜索按此顺序进行:
C
(包括继承的嵌套类型)MyCorp.TheProduct.SomeModule.Utilities
MyCorp.TheProduct.SomeModule
MyCorp.TheProduct
MyCorp
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;
}
}
现在,搜索类型 Ambiguous
按顺序排列:
C
(包括继承的嵌套类型)MyCorp.TheProduct.SomeModule.Utilities
System
, System.Collections.Generic
, System.Linq
, MyCorp.TheProduct
, MyCorp.TheProduct.OtherModule
, MyCorp.TheProduct.OtherModule.Integration
,和 ThirdParty
MyCorp.TheProduct.SomeModule
MyCorp
(注意 MyCorp.TheProduct
是“3”的一部分因此在“4.”之间不需要和“5.”。)
结束语
无论你是将命令放在命名空间声明的内部还是外部,总有可能以后有人将具有相同名称的新类型添加到具有更高优先级的命名空间之一。
此外,如果嵌套命名空间与类型具有相同的名称,则可能会导致问题。
将使用从一个位置移动到另一个位置总是很危险的,因为搜索层次结构发生了变化,可能会找到另一种类型。因此,选择一个约定并坚持下去,这样你就不必再使用它了。
默认情况下,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;
}
根据 汉塞尔曼 - 使用指令和装配加载...... 和其他这样的文章在技术上没有区别。
我的偏好是将它们放在命名空间之外。
根据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的来源消除。
在单个文件中放置多个名称空间元素通常是一个坏主意,但是如果这样做,最好将所有using指令放在每个名称空间元素中,而不是全局放在文件的顶部。这将严格限定命名空间的范围,并且还有助于避免上述类型的行为。
重要的是要注意,当使用位于命名空间之外的using指令编写代码时,在命名空间中移动这些指令时应小心,以确保这不会改变代码的语义。如上所述,在namespace元素中放置using-alias指令允许编译器以指令放置在命名空间之外时不会发生的方式在冲突类型之间进行选择。
如何修复违规行为 要修复违反此规则的行为,请在namespace元素中移动所有using指令和using-alias指令。
当您希望使用别名时,在命名空间内放置使用语句会出现问题。别名不会从早期受益 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
都在同一个全局命名空间中, 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;
}
}
技术原因在答案中进行了讨论,我认为最终涉及个人偏好,因为差异并非如此 大 并且两者都存在权衡。 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"
}
}
}
您可以在解决方案级别创建此配置文件,并将其作为“现有链接文件”添加到项目中,以便在所有项目中共享配置。