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

是否有技术上的理由将using指令放置在名称空间内而不是在名称空间外?

评论

有时,您放置使用的位置会有所不同:stackoverflow.com/questions/292535/linq-to-sql-designer-bug

仅作为参考,它的含义不只是每个文件多个类的问题,因此,如果您是这个问题的新手,请继续阅读。

@ user-12506-在需要一定程度的代码一致性的中型到大型开发团队中,这不能很好地工作。如前所述,如果您不了解不同的布局,可能会发现边缘盒无法按预期工作。

术语:不使用语句;他们正在使用指令。另一方面,using语句是一种语言结构,它与方法体内的其他语句等一起出现。例如,使用(var e = s.GetEnumerator()){/ * ... * /}与var e = s.GetEnumerator()大致相同的语句;最后尝试{/ * ... * /} {如果(e!= null){e.Dispose(); }。

如果任何人都没有提到这一点,那么实际上Microsoft也建议在其内部编码准则中将using语句放在名称空间声明内

#1 楼

两者之间实际上存在(细微)差异。假设您在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指令之前会搜索最内层的封闭名称空间。

#2 楼

这个线程已经有了一些不错的答案,但是我觉得我可以用这个额外的答案来详细介绍一下。

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

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中的类型

空名称空间(全局名称空间)中的类型
SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty中的类型


另一个约定:

(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进入此order:


C内部的嵌套类型(包括继承的嵌套类型)
当前命名空间中的类型MyCorp.TheProduct.SomeModule.Utilities

SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProductMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty中的类型

名称空间MyCorp.TheProduct.SomeModule中的类型

MyCorp中的类型

空名称空间(全局名称空间)中的类型

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

结束语

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

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

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

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

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

#3 楼

将其放在名称空间中会使声明在该文件的名称空间中处于本地状态(如果文件中有多个名称空间),但是如果每个文件只有一个名称空间,则它们在外部还是外部都没有多大区别。在名称空间中。

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}


评论


名称空间提供逻辑上的分隔,而不是物理上的(文件)分隔。

– Jowen
2013年9月26日上午11:08

没有区别是不完全正确的。在命名空间块中使用指令可以基于封闭的命名空间块引用相对的命名空间。

– O. R. Mapper
2014年2月9日在10:07

是的,我知道。我们在五年前确定了该问题的公认答案。

– Mark Cidade
2014年2月10日在21:58

#4 楼

根据Hanselman-使用指令和程序集加载...和其他此类文章,在技术上没有区别。

我的首选是将它们放在名称空间之外。

评论


@Chris M:呃...答案中张贴的链接表明进与出没有好处,实际上显示了一个示例,该示例伪造了您张贴的链接中的主张...

–约翰尼
2011年8月19日在18:07

是的,我没有完全阅读该主题,但在MVP表示是正确的时候买了。一个家伙反驳了它,解释了它,并进一步展示了他的代码...“在任何情况下,C#编译器生成的IL都是相同的。实际上,C#编译器恰好不生成与每个using指令相对应的内容。using指令纯粹是一个C#ism,它们对.NET本身没有任何意义。(使用语句不是正确的,但是有些不同。)“ groups.google.com/group/wpf-disciples/msg/781738deb0a15c46

–克里斯·麦克基(Chris McKee)
2011年8月24日在21:52



请提供链接摘要。当链接断开时(因为它会在足够的时间后发生),突然有了32个投票的答案才值得。我的风格是将其放置在命名空间之外。 -根本没有答案。

– ANeves认为SE是邪恶的
2012年10月22日7:45

这里的说法完全是错误的……存在技术上的差异,您自己的引用也是如此……实际上,这就是全部。请删除这个错误的答案……还有更好,更准确的答案。

–吉姆·巴尔特(Jim Balter)
16-4-17的3:44

#5 楼

根据StyleCop文档:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

由于
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指令放置在namespace元素之外时,编译器将选择在本地命名空间内定义的Guid的本地定义,并完全忽略在namespace外部定义的using-alias指令。不幸的是,在阅读代码时这并不明显。

但是,当using-alias指令位于名称空间中时,编译器必须在两个不同的,相互冲突的Guid类型之间进行选择,两者都在同一名称空间中定义。这两种类型都提供匹配的构造函数。编译器无法做出决定,因此会标记出编译器错误。

将using-alias指令放置在名称空间之外是一种不好的做法,因为在这种情况下,这可能导致混乱,实际使用哪种类型的版本并不明显。这可能会导致可能难以诊断的错误。

在名称空间元素中使用using-alias指令放置可消除此错误来源。


多个命名空间

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

请注意,当使用放置在命名空间之外的指令编写代码时,应注意当在命名空间中移动这些指令时,应采取这种措施,以确保这不会改变代码的语义。如上文所述,将using-alias指令放置在namespace元素中,可使编译器以冲突的类型进行选择,而方式必须是将指令放置在命名空间之外时不会发生。

如何解决冲突
要解决违反此规则的问题,请在命名空间元素内移动所有using指令和using-alias指令。

评论


@Jared-正如我在回答中指出的那样,我更喜欢的解决方法/解决方案是每个文件只能有一个类。我认为这是一个相当普遍的约定。

–benPearce
09年9月14日在21:01

确实,这也是StyleCop规则! SA1402:C#文档在根级别只能包含一个类,除非所有类都是部分类并且属于同一类型。通过打破另一条规则来展示一条规则,只是滴上了错误的酱汁。

–任务
2010-3-5在18:09



赞扬它是从StyleCop角度实际覆盖它的第一个答案。我个人喜欢在命名空间之外使用的视觉效果。内部使用对我来说看起来很丑。 :)

– nawfal
15年6月6日在10:45

终于很好地回答了这个问题。而且benPearce的评论是无关紧要的……这与文件中的类数无关。

–吉姆·巴尔特(Jim Balter)
16-4-17的3:51

#6 楼

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


不漂亮。

评论


您的班级需要一个名称(标识符)。如您所指示的,您不能在类内使用using指令。它必须在名称空间级别上,例如在最外部名称空间之外,或仅在最内部名称空间内部(但不能在类/接口/等内部)。

–杰普·斯蒂格·尼尔森(Jeppe Stig Nielsen)
16 Mar 23 '16 at 11:51

@JeppeStigNielsen谢谢。我错误地将using指令放错了位置。我已经对其进行了编辑,使其达到了预期的效果。感谢您指出。不过,推理还是一样的。

– Neo
16 Mar 23 '16 at 20:54

#7 楼

我遇到了一个折皱(其他答案未解决):

假设您具有以下名称空间:


Something.Other
父母.Something.Other

当您在using Something.Other之外使用namespace Parent时,它指的是第一个(Something.Other)。

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

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

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


#8 楼

正如Jeppe Stig Nielsen所说,该线程已经有了不错的答案,但我认为这种明显的微妙之处也值得一提。

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回答中的别名除外)应该超出命名空间,以强制使用完全限定的命名空间名称。

–基思·罗伯逊(Keith Robertson)
16年3月3日在15:39

#9 楼

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

当您在名称空间中包含导入时,它将找到类。如果导入位于名称空间之外,则导入将被忽略,并且类和名称空间必须完全合格。

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


#10 楼

当引用Microsoft的内部准则时,请记住,它们是由可能具有不到十年编码经验的人编写的。换句话说,他们可能只是基于个人喜好而已。尤其是在像C#这样的新事物中。
通常,应将外部using指令(例如,系统和Mi​​crosoft名称空间)放在namespace指令之外。除非另有说明,否则它们是应在所有情况下应用的默认值。这应该包括不属于当前项目的您自己组织的任何内部库,或引用同一项目中其他主要名称空间的using指令。任何引用当前项目和名称空间中其他模块的using指令都应放在namespace指令内。它具有两个特定的功能:

它在本地模块和“其他”模块之间进行了直观的区分,这意味着其他所有内容。
它使局部指令的适用范围比全局指令优先。

后一个原因很重要。这意味着要引入一个模棱两可的参考问题比较困难,该问题可以通过更改来实现,而更改仅比重构代码重要。就是说,您将一种方法从一个文件移到另一个文件,突然出现了以前没有的错误。通俗地说,是一个'heisenbug'-历史上很难追踪。
作为更普遍的规则,这是一个值得遵循的规则。如果您看到某种语言固有的东西似乎是无用的选择,请假定它不是。实际上,越难理解该选项的存在原因,就应该假设它越重要。对两种选择之间的具体差异进行研究,然后仔细思考其含义。通常,您会发现一个难以理解的,聪明的解决方案,可以解决语言设计师专门为了使您的生活更轻松而解决的一个晦涩问题。适当地感恩并善加利用。

评论


另一方面,功能设计师可能是一个在实习项目中工作的本科生,他首先向几乎没有经验的处理人员提出了新功能。但是,不同意您的其他观点。

– jwdonahue
12月3日18:32

#11 楼

答案中讨论了技术原因,我认为最终涉及的是个人喜好,因为两者之间的差异并不大,而且两者都需要权衡取舍。 Visual Studio的用于创建.cs文件的默认模板在命名空间之外使用using指令,例如,可以通过在项目文件的根目录中添加using文件,并通过以下操作来调整stylecop以检查命名空间之外的stylecop.json指令: br />
{
  "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace"
    }
  }
}


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

#12 楼

如果在源解决方案中使用默认值(即“引用”)的默认值应该在命名空间之外,而那些“新添加的引用”的默认值应该在名称空间中,则是一种更好的做法。这是为了区分要添加的引用。

评论


不,实际上这是一个坏主意。您不应基于使用指令是在本地范围内还是在全局范围内使用指令之间的位置,无论它们是否是新添加的。取而代之的是,最好按字母顺序排列它们,但BCL引用除外,该引用应放在最前面。

–阿贝尔
2014年11月3日14:01