我一直在通过一些 C# 代码运行StyleCop,它不断报告我的using
指令应该在命名空间内。
using
将指令放在命名空间内部而不是外部是否有技术原因?
我一直在通过一些 C# 代码运行StyleCop,它不断报告我的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
那些指令之前进行搜索,因此它会找到而不是. 不幸的是(或者幸运的是?),没有成员,所以 File1 现在坏了。using
Outer.Math
System.Math
Outer.Math
PI
如果您将using
命名空间声明放在内部,则会发生这种情况,如下所示:
// File1b.cs
namespace Outer.Inner
{
using System;
class Foo
{
static void Bar()
{
double d = Math.PI;
}
}
}
现在编译器在 search , findsSystem
之前搜索,一切都很好。Outer
System.Math
有些人会争辩说,Math
这对于用户定义的类来说可能是一个坏名字,因为 ; 中已经有一个了System
。这里的重点只是存在差异,它会影响代码的可维护性。
Foo
注意到如果在 namespace 中会发生什么也很有趣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) 里面有 usings:
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.”之间不需要。)
结束语
无论您将 usings 放在命名空间声明内部还是外部,总有可能有人稍后将具有相同名称的新类型添加到具有更高优先级的命名空间之一。
此外,如果嵌套命名空间与类型同名,则可能会导致问题。
将 usings 从一个位置移动到另一个位置总是很危险的,因为搜索层次结构会发生变化,并且可能会找到另一种类型。因此,选择一种约定并坚持下去,这样您就不必移动 usings 了。
默认情况下,Visual Studio 的模板将 usings放在命名空间之外(例如,如果您让 VS 在新文件中生成新类)。
在外部使用 usings 的一个(微小)优势是您可以将 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 - Using Directive and Assembly Loading...和其他此类文章在技术上没有区别。
我的偏好是将它们放在命名空间之外。
根据 StyleCop 文档:
SA1200: Using DirectivesMustBePlacedWithinNamespace
原因 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 指令放在命名空间元素之外时,编译器将选择本地命名空间内定义的 Guid 的本地定义,而完全忽略命名空间外定义的 using-alias 指令。不幸的是,这在阅读代码时并不明显。
但是,当 using-alias 指令位于命名空间内时,编译器必须在两个不同的、冲突的 Guid 类型之间进行选择,这两种类型都定义在同一个命名空间内。这两种类型都提供了匹配的构造函数。编译器无法做出决定,因此它会标记编译器错误。
将 using-alias 指令放在命名空间之外是一种不好的做法,因为在这种情况下可能会导致混淆,在这种情况下,实际使用的是哪个版本的类型并不明显。这可能会导致可能难以诊断的错误。
在命名空间元素中放置 using-alias 指令可以消除此错误的来源。
在单个文件中放置多个命名空间元素通常不是一个好主意,但如果这样做了,最好将所有 using 指令放置在每个命名空间元素中,而不是全局放置在文件顶部。这将严格限定命名空间,也有助于避免上述行为。
重要的是要注意,当使用放置在命名空间之外的 using 指令编写代码时,在命名空间内移动这些指令时应小心,以确保这不会改变代码的语义。如上所述,在命名空间元素中放置 using-alias 指令允许编译器以在将指令放置在命名空间之外时不会发生的方式在冲突类型之间进行选择。
如何修复违规 要修复违反此规则的行为,请在命名空间元素中移动所有 using 指令和 using-alias 指令。
当您希望使用别名时,在命名空间内放置 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>>>;
不漂亮。
我遇到的一个皱纹(其他答案中没有涉及):
假设您有这些命名空间:
当您在 a 之外使用时using Something.Other
,namespace Parent
它指的是第一个(Something.Other)。
但是,如果您在该命名空间声明中使用它,它指的是第二个(Parent.Something.Other)!
有一个简单的解决方案:添加“ global::
”前缀:docs
namespace Parent
{
using global::Something.Other;
// etc
}
正如 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;
}
}
我认为其他答案未涵盖的另一个微妙之处是当您有一个具有相同名称的类和命名空间时。
当您在命名空间内进行导入时,它将找到该类。如果导入在命名空间之外,那么导入将被忽略,并且类和命名空间必须是完全限定的。
//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.
}
}
}
技术原因在答案中进行了讨论,我认为最终涉及个人喜好,因为差异并不大,并且两者都有权衡。Visual Studio 用于创建.cs
文件的默认模板使用using
命名空间之外的指令,例如
可以通过在项目文件的根目录中using
添加文件来调整 stylecop 以检查命名空间之外的指令:stylecop.json
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
}
}
}
您可以在解决方案级别创建此配置文件并将其作为“现有链接文件”添加到您的项目中,以便在您的所有项目中共享配置。
通常,外部using
指令(例如 System 和 Microsoft 命名空间)应放在指令之外。除非另有说明,否则namespace
它们是应在所有情况下应用的默认值。这应该包括您自己组织的任何不属于当前项目的内部库,或引用同一项目中其他主要命名空间的指令。任何引用当前项目和命名空间中其他模块的指令都应该放在指令内。这有两个特定的功能:using
using
namespace
后一个原因很重要。这意味着更难引入不明确的参考问题,而这些问题可能由不比重构代码更重要的更改引入。也就是说,您将一个方法从一个文件移动到另一个文件,然后突然出现一个以前不存在的错误。通俗地说,一个“heisenbug”——在历史上极其难以追踪。
如果源解决方案中使用的默认使用即“引用”应该在命名空间之外,而那些“新添加的引用”是一个很好的做法,那么最好将其放在命名空间内。这是为了区分正在添加的引用。