我一直在一些C#代码上运行StyleCop,它不断报告我的using指令应该在命名空间中。
是否有技术原因将using指令放在命名空间内部而不是外部?
我一直在一些C#代码上运行StyleCop,它不断报告我的using指令应该在命名空间中。
是否有技术原因将using指令放在命名空间内部而不是外部?
当前回答
通常,外部using指令(例如System和Microsoft命名空间)应该放在命名空间指令之外。除非另有规定,否则应在所有情况下应用默认值。这应该包括不属于当前项目的任何组织内部库,或者使用引用同一项目中其他主名称空间的指令。引用当前项目和命名空间中其他模块的任何using指令都应放在命名空间指令中。这有两个特定的功能:
它提供了本地模块和“其他”模块之间的视觉区别,这意味着所有其他模块。它将本地指令的范围限定为优先于全局指令应用。
后一个原因很重要。这意味着很难引入一个不明确的引用问题,而这个引用问题可能是由一个比重构代码更重要的更改引起的。也就是说,你将一个方法从一个文件移动到另一个文件,突然出现了一个以前不存在的bug。通俗地说,一个“海森堡”——历史上极难追踪。
其他回答
事实上,两者之间有(微妙的)区别。假设您在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,因此找到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,会发生什么也很有趣。在这种情况下,在File2中添加Outer.Math会破坏File1,而不管使用的位置如何。这意味着编译器在查看任何using指令之前搜索最内部的封闭命名空间。
这个主题已经有了一些很好的答案,但我觉得我可以用这个额外的答案带来更多的细节。
首先,请记住带有句点的命名空间声明,如:
namespace MyCorp.TheProduct.SomeModule.Utilities
{
...
}
完全等同于:
namespace MyCorp
{
namespace TheProduct
{
namespace SomeModule
{
namespace Utilities
{
...
}
}
}
}
如果您愿意,可以在所有这些级别上使用指令。(当然,我们只想在一个地方使用,但根据语言的不同,这是合法的。)
解析隐含类型的规则可以大致如下:首先在最内部的“范围”中搜索匹配项,如果没有找到匹配项,则从一个级别转到下一个范围,然后在那里搜索,依此类推,直到找到匹配项。如果在某个级别上找到多个匹配项,如果其中一个类型来自当前程序集,请选择该类型并发出编译器警告。否则,放弃(编译时错误)。
现在,让我们在两个主要约定的具体示例中明确这意味着什么。
(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.Other Module.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和第三方中的类型命名空间MyCorp.TheProduct.SomeModule中的类型MyCorp中的类型空命名空间(全局命名空间)中的类型
(注意MyCorp.TheProduct是“3.”的一部分,因此在“4.”和“5.”之间不需要。)
结束语
无论您将using放在名称空间声明的内部还是外部,都有可能有人稍后将具有相同名称的新类型添加到其中一个具有较高优先级的名称空间中。
此外,如果嵌套命名空间与类型同名,则可能会导致问题。
将using从一个位置移动到另一个位置总是很危险的,因为搜索层次结构发生了变化,可能会找到另一种类型。因此,选择一个惯例并坚持它,这样你就不必再使用。
默认情况下,VisualStudio的模板将using放在命名空间之外(例如,如果您使VS在新文件中生成新类)。
在外部使用using的一个(微小的)优点是,您可以使用全局属性的using指令,例如[assembly:ComVisible(false)]而不是[assembly:System.Runtime.InteropServices.ComVisible。
关于文件范围的命名空间声明的更新
由于C#10.0(从2021开始),您可以避免缩进,并使用以下两种方法之一(惯例1,在外部使用):
using System;
using System.Collections.Generic;
using System.Linq;
using MyCorp.TheProduct.OtherModule;
using MyCorp.TheProduct.OtherModule.Integration;
using ThirdParty;
namespace MyCorp.TheProduct.SomeModule.Utilities;
class C
{
Ambiguous a;
}
或(惯例2,内部使用):
namespace MyCorp.TheProduct.SomeModule.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using MyCorp.TheProduct;
using MyCorp.TheProduct.OtherModule;
using MyCorp.TheProduct.OtherModule.Integration;
using ThirdParty;
class C
{
Ambiguous a;
}
但同样的考虑也适用。
尚未提及:
将using指令放在命名空间声明中是众所周知的最佳编程实践的一个应用,即在尽可能小的范围内声明所有内容。
如果最佳编程实践是您的第二天性,那么您可以自动执行类似的操作。
这可能是将using指令放在命名空间声明中的最佳理由,而不管其他地方提到的(边界)技术(边界)优点;就这么简单。
已经提到了,但可能更好地说明了:
在名称空间中放置using指令可以避免不必要的重复,并使声明更加简洁。
这是不必要的简洁:
using Com.Acme.Products.Traps.RoadRunnerTraps;
namespace Com.Acme.Products.Traps {
这是甜蜜的,切中要害:
namespace Com.Acme.Products.Traps {
using RoadRunnerTraps;
根据Hanselman-使用指令和装配加载。。。和其他此类物品在技术上没有区别。
我的偏好是将它们放在名称空间之外。
当您希望使用别名时,在名称空间中放置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 MyAlias = System.Tuple<System.Linq.Expressions.Expression<System.Func<System.DateTime, object>>, System.Linq.Expressions.Expression<System.Func<System.TimeSpan, object>>>;
不漂亮。