'using'指令应该在命名空间的内部还是外部?

问题描述 投票:1890回答:10

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

是否存在将using指令放入命名空间而不是命名空间外的技术原因?

c# .net namespaces stylecop code-organization
10个回答
1978
投票

两者之间实际上存在(微妙的)差异。想象一下,你在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中添加using都会破坏File1。这意味着编译器在查看任何using指令之前搜索最里面的封闭命名空间。


0
投票

在答案中讨论了技术原因,我认为最终涉及个人偏好,因为差异并不大,而且两者都存在权衡。 Visual Studio的用于创建//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. } } } 文件的默认模板使用命名空间之外的using Something.Other指令,例如

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

docs

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


-8
投票

如果那些默认使用,即源解决方案中使用的“引用”应该在命名空间之外,而那些是“新添加的引用”是一个好的做法,那么你应该将它放在命名空间中。这是为了区分添加的引用。


398
投票

这个帖子已经有了一些很好的答案,但我觉得我可以通过这个额外的答案带来更多细节。

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

namespace MyCorp.TheProduct.SomeModule.Utilities
{
    ...
}

完全等同于:

namespace MyCorp
{
    namespace TheProduct
    {
        namespace SomeModule
        {
            namespace Utilities
            {
                ...
            }
        }
    }
}

如果你愿意,你可以将using指令放在所有这些级别上。 (当然,我们希望只在一个地方使用usings,但根据语言它是合法的。)

解析哪种类型的规则可以这样松散地说:首先搜索最内部的“范围”以进行匹配,如果没有找到任何内容,那么将一个级别输出到下一个范围并在那里搜索,依此类推,直到找到匹配项。如果在某个级别找到多个匹配项,如果其中一个类型来自当前程序集,则选择该类型并发出编译器警告。否则,放弃(编译时错误)。

现在,让我们在两个主要约定的具体例子中明确这意味着什么。

(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. null命名空间中的类型(全局命名空间)
  7. 在qazxsw poi,qazxsw poi,qazxsw poi,SystemSystem.Collections.GenericSystem.Linq中的类型

另一个惯例:

(2)内部使用:

MyCorp.TheProduct.OtherModule

现在,按以下顺序搜索MyCorp.TheProduct.OtherModule.Integration类型:

  1. 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; } } 中的类型
  3. 在qazxsw poi,qazxsw poi,qazxsw poi,AmbiguousCMyCorp.TheProduct.SomeModule.UtilitiesSystem中的类型
  4. 命名空间System.Collections.Generic中的类型
  5. System.Linq中的类型
  6. null命名空间中的类型(全局命名空间)

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

结束语

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

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

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

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

在外部使用的一个(微小的)优点是,您可以将using指令用于全局属性,例如MyCorp.TheProduct.OtherModule而不是MyCorp.TheProduct.OtherModule.Integration


188
投票

将它放在命名空间内会使文件的该命名空间的声明是本地的(如果文件中有多个命名空间),但是如果每个文件只有一个命名空间,那么它们是否在外面或者它们没有多大区别在命名空间内。

ThirdParty

61
投票

根据MyCorp.TheProduct.SomeModule和其他此类文章,技术上没有区别。

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


47
投票

根据StyleCop文档:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

原因C#using指令放在名称空间元素之外。

规则说明如果将using指令或using-alias指令放在namespace元素之外,则会违反此规则,除非该文件不包含任何名称空间元素。

例如,以下代码将导致两次违反此规则。

MyCorp

但是,以下代码不会导致违反此规则:

MyCorp.TheProduct

此代码将干净地编译,没有任何编译器错误。但是,尚不清楚正在分配哪种版本的Guid类型。如果在命名空间内移动using指令,如下所示,将发生编译器错误:

[assembly: ComVisible(false)]

代码在包含[assembly: System.Runtime.InteropServices.ComVisible(false)]的行上找到的以下编译器错误失败

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的来源消除。

  1. 多个命名空间

在单个文件中放置多个名称空间元素通常是一个坏主意,但是如果这样做,最好将所有using指令放在每个名称空间元素中,而不是全局放在文件的顶部。这将严格限定命名空间的范围,并且还有助于避免上述类型的行为。

重要的是要注意,当使用位于命名空间之外的using指令编​​写代码时,在命名空间中移动这些指令时应小心,以确保这不会改变代码的语义。如上所述,在namespace元素中放置using-alias指令允许编译器以指令放置在命名空间之外时不会发生的方式在冲突类型之间进行选择。

如何修复违规要修复违反此规则的行为,请在namespace元素中移动所有using指令和using-alias指令。


32
投票

当您希望使用别名时,在命名空间内放置使用语句会出现问题。别名不会受益于早期的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...

与:

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

如果您有一个冗长的别名,例如以下(这是我发现问题的方式),这可能会特别明显:

namespace Microsoft.Sample
{
    using System;
    using Guid = System.Guid;

    public class Program
    {
    }
}

使用命名空间内的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");

不漂亮。


3
投票

作为Jeppe Stig Nielsen 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中。

设定代码文件Foo.cs:

using MyAlias = System.Tuple<System.Linq.Expressions.Expression<System.Func<System.DateTime, object>>, System.Linq.Expressions.Expression<System.Func<System.TimeSpan, object>>>;

和Bar.cs:

said

这可能会省略using指令中的外部命名空间,简而言之:

Foo

1
投票

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

当您在命名空间内导入时,它将找到该类。如果导入位于命名空间之外,则将忽略导入,并且类和命名空间必须完全限定。

Bar

1
投票

我碰到了一个皱纹(其他答案没有涉及):

假设您有这些名称空间:

  • Something.Other
  • Parent.Something.Other

当你在Outer之外使用namespace Outer.Inner { class Foo { } } 时,它指的是第一个(Something.Other)。

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

有一个简单的解决方案:添加“namespace Outer { using Outer.Inner; class Bar { public Foo foo; } } ”前缀:using

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.