ChatGPT解决这个技术问题 Extra ChatGPT

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

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

using 指令放在命名空间内部而不是外部是否有技术原因?

有时,您在哪里放置 usings 会有所不同:stackoverflow.com/questions/292535/linq-to-sql-designer-bug
仅供参考,除了每个文件多个类的问题之外,还有其他含义,所以如果您不熟悉这个问题,请继续阅读。
@user-12506 - 这在需要一定程度的代码一致性的中型到大型开发团队中效果不佳。如前所述,如果您不了解不同的布局,您可能会发现边缘情况无法按预期工作。
术语:这些不是using 陈述;它们是using 指令。另一方面,using 语句是一种语言结构,它与方法主体等内的其他语句一起出现。例如,using (var e = s.GetEnumerator()) { /* ... */ } 是与 var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } } 大致相同的语句。
如果任何人都没有提到这一点,实际上 Microsoft 也建议将 using 语句放在 namespace 声明中,在他们的 internal coding guidlines

P
Philippe

两者之间实际上存在(细微的)差异。假设您在 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;
        }
    }
}

现在编译器在搜索 Outer 之前搜索 System,找到 System.Math,一切正常。

有些人会争辩说,Math 对于用户定义的类来说可能是个坏名字,因为 System 中已经有一个;这里的重点只是的不同,它会影响代码的可维护性。

如果 Foo 位于命名空间 Outer 而不是 Outer.Inner 中,会发生什么也很有趣。在这种情况下,无论 using 走到哪里,在 File2 中添加 Outer.Math 都会中断 File1。这意味着编译器会在查看任何 using 指令之前搜索最里面的封闭命名空间。


Fundamentals - program-structure 表示 c#-9.0 .net-6.0using directives must come first in the file。有谁知道这对上面的例子意味着什么?
@surfmuggle 如果我没记错的话,我相信您的链接专门指的是没有主要方法的控制台应用程序。
J
Jeppe Stig Nielsen

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

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

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

完全等同于:

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

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

解析隐含类型的规则可以大致如下表述:首先在最里面的“范围”中搜索匹配项,如果没有找到匹配项,则转到下一级范围并在那里搜索,依此类推,直到找到匹配项。如果在某个级别找到多个匹配项,如果其中一种类型来自当前程序集,则选择该类型并发出编译器警告。否则,放弃(编译时错误)。

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

(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 中的类型 null 命名空间(全局命名空间)中的类型System、System.Collections.Generic、System.Linq、MyCorp.TheProduct.OtherModule、MyCorp.TheProduct.OtherModule.Integration 和第三方

另一个约定:

(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 中的类型。命名空间 MyCorp.TheProduct.SomeModule 中的 OtherModule.Integration 和第三方类型 MyCorp 中的类型 null 命名空间(全局命名空间)中的类型

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

结束语

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

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

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

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

使用 usings outside 的一个(微小)优势是您可以将 using 指令用于全局属性,例如 [assembly: ComVisible(false)] 而不是 [assembly: System.Runtime.InteropServices.ComVisible(false)]

关于文件范围命名空间声明的更新

自 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;
}

但与之前相同的考虑也适用。


刚刚有一个案例,我继承的类库在 global/null 命名空间中有一些类。其中一个类名与我正在使用的命名空间内的类名相同。 'using' 在命名空间定义之外。我不明白为什么它总是会选择全局类的定义。偶然地,将命名空间放在外面 - 它拿起了我需要的类。你的回答解释了原因。在命名空间之外,null/globals 最后被拾取。对我来说,这是正确/预期的行为。
@PaulEvans您的案例也是一个示例,为什么永远不应该在全局命名空间中放置任何东西,尤其是任何公共的东西。您使用的库违反了这一点。
M
Mark Cidade

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

namespace Namespace1
{ 
   using ThisNamespace.IsImported.InNamespace1.AndNamespace2;

   namespace Namespace2
   { 
      using ThisNamespace.IsImported.InJustNamespace2;
   }       
}

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

命名空间提供了一种逻辑分隔,而不是物理(文件)分隔。
没有区别并不完全正确。 namespace 块中的 using 指令可以基于封闭的 namespace 块引用相对命名空间。
是的,我知道。五年前,我们在这个问题的公认答案中确定了这一点。
Q
Quintin Robinson

根据 Hanselman - Using Directive and Assembly Loading... 和其他此类文章,在技术上没有区别。

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


@Chris M:呃......答案中发布的链接表明in vs. out没有任何好处,实际上显示了一个伪造您发布的链接中声明的示例......
是的,我没有完全阅读该线程,但是当 MVP 说它是正确的时我接受了。一个人反驳它,解释它并进一步展示他的代码......“C#编译器生成的IL在任何一种情况下都是相同的。事实上,C#编译器不会生成任何对应于每个using指令的内容。使用指令纯粹是一个C#ism,它们对 .NET 本身没有任何意义。(对于 using 语句而言并非如此,但它们完全不同。)“groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
请附上链接的摘要。 链接断开时(因为它会发生,只要有足够的时间),突然间,一个有 32 个赞成票的答案只值 My style is to put them outside the namespaces. - 几乎没有答案。
这里的说法是完全错误的......存在技术差异,您自己的引文是这样说的......事实上,这就是它的全部内容。请删除这个错误的答案......有更好,更准确的答案。
OP 在他们的回答中表示“技术上没有区别”,他们是正确的 - 从技术角度来看没有区别。声称这是一个错误的答案是完全错误的
J
JaredCacurak

根据 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 指令。


@Jared - 正如我在回答中指出的那样,我更喜欢的解决方法/解决方案是每个文件只有一个类。我认为这是一个相当普遍的约定。
确实,这也是 StyleCop 规则! SA1402:AC# 文档在根级别只能包含一个类,除非所有类都是部分的并且属于同一类型。通过打破另一条规则来展示一条规则只会滴下错误的酱汁。
赞成作为第一个从 StyleCop 角度实际覆盖它的答案。就我个人而言,我喜欢命名空间之外的 using 的视觉感受。 Inner using 对我来说太丑了。 :)
终于很好地回答了这个问题。而benPearce的评论是无关紧要的......这与文件中的类数无关。
N
Neo

当您希望使用别名时,在命名空间内放置 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>>>;

不漂亮。


您的 class 需要一个名称(标识符)。如您所指出的,您不能在类中包含 using 指令。它必须在命名空间级别,例如在最外面的 namespace 之外,或者就在最里面的 namespace 内部(但不在类/接口/等内部)。
@JeppeStigNielsen 谢谢。我错误地放错了 using 指令。我已经将它编辑成我想要的样子。感谢您指出。不过,道理还是一样的。
H
Hans Kesting

我遇到的一个皱纹(其他答案中没有涉及):

假设您有这些命名空间:

东西。其他

父母.某事.其他

当您在 namespace Parentusing Something.Other outside 中使用时,它指的是第一个 (Something.Other)。

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

有一个简单的解决方案:添加“global::”前缀:docs

namespace Parent
{
   using global::Something.Other;
   // etc
}

好点子!我在使用 RazorGenerator 时遇到了相关问题。不幸的是,它似乎不理解'global ::'并将其转换为'using global;' :( 很遗憾我无法选择是否需要在“命名空间”内部或外部生成“使用”...
J
Jeppe Stig Nielsen

我认为其他答案未涵盖的另一个微妙之处是当您有一个具有相同名称的类和命名空间时。

当您在命名空间内进行导入时,它将找到该类。如果导入在命名空间之外,那么导入将被忽略,并且类和命名空间必须是完全限定的。

//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();
        }
    }
}

//file3.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.
        }
    }
}

我认为这可以通过我在上面的答案中给出的搜索顺序来解释。如您所说,在 file3.cs 中您必须说 Foo.Foo。那里的 using 指令没用。但是,在 file2.cs 中,您无权访问命名空间。就像您在 Main 方法中说 Foo.DeeperNamespace.SomeClass 一样,它只会在 Foo 类中搜索,而不是在 Foo 命名空间内。所以你需要global::Foo.DeeperNamespace。在任何情况下,所有指南都建议反对对命名空间和类型使用相同的名称。
C
Community

作为 Jeppe Stig Nielsen said,这个线程已经有了很好的答案,但我认为这个相当明显的微妙之处也值得一提。

using 在命名空间内指定的指令可以缩短代码,因为它们不需要像在外部指定时那样完全限定。

以下示例有效,因为类型 FooBar 都在同一个全局命名空间 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;
    }
}

您确实“可以省略外部命名空间”,但这并不意味着您应该这样做。对我来说,这是关于为什么使用指令(@Neo 的答案中的别名除外)应该超出命名空间以强制使用完全限定的命名空间名称的另一个论点。
s
sotn

答案中讨论了技术原因,我认为最终涉及个人偏好,因为差异不是那么,并且两者都有权衡。 Visual Studio 用于创建 .cs 文件的默认模板使用命名空间之外的 using 指令,例如

可以通过在项目文件的根目录中添加 stylecop.json 文件来调整 stylecop 以检查命名空间之外的 using 指令,其中包含以下内容:

{
  "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace"
    }
  }
}

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


Y
Yrth

作为一项规则,外部 using 指令(例如系统和 Microsoft 命名空间)应放置在 外部 namespace 指令。它们是应在所有情况下应用的默认值除非另有说明。这应包括您自己组织的任何不属于当前项目的内部库,或引用同一项目中其他主要命名空间的 using 指令。任何引用当前项目和命名空间中其他模块的 using 指令都应放置在 内部 namespace 指令中。这有两个特定的功能:

它提供了本地模块和“其他”模块之间的视觉区别,即其他所有模块。

它将本地指令的范围限定为优先于全局指令应用。

后一个原因很重要。这意味着更难引入不明确的参考问题,而这些问题可能由不比重构代码更重要的更改引入。也就是说,您将一个方法从一个文件移动到另一个文件,然后突然出现一个以前不存在的错误。通俗地说,一个“heisenbug”——在历史上极其难以追踪。


@jwdonahue:当我被邀请加入 C# 特性设计团队时,我已经是一名专业的编译器开发人员和语言设计师近十年了,而且我是房间里最年轻的人。 C# 设计团队肯定会接受来自实习生的好主意; C# 设计团队在设计、评估、指定和实现语言特性方面缺乏经验的观点是不符合事实的。
同样,这个答案的开头是一个奇怪的和完全相反的事实假设,即框架设计指南的作者只是列出了他们的偏好。没有东西会离事实很远。我们在一个满是专家和利益相关者的房间里对这些指导方针进行了激烈的辩论;它们是仔细考虑专业开发人员需求的产物。
@EricLippert,事后看来,这是一个愚蠢的想法,我不应该说出来。道歉。
我认为这是一个可靠的答案,但在第一段和最后一段中被削弱了(并且可能因为投票而被否决)。我建议只删除第一段和最后一段。
@andrewf 很高兴看到回复提供了一些可靠的想法来改进帖子。我的感谢。虽然我不太明白最后一段的问题......
I
Israel Ocbina

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


不,实际上这是个坏主意。您不应将 using 指令的本地范围和全局范围之间的位置基于它们是否新添加的事实。相反,最好将它们按字母顺序排列,但 BCL 引用除外,它应该放在最前面。