首页 文章

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

提问于
浏览
1811

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

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

10 回答

  • 3

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

    在命名空间内指定的 using 指令可以生成更短的代码,因为它们在外部指定了't need to be fully qualified as when they' .

    以下示例有效,因为 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;
        }
    }
    
  • 58

    根据StyleCop文档:

    SA1200:UsingDirectivesMustBePlacedWithinNamespace

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

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

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

    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'冲突的定义

    该代码创建了一个名为Guid的System.Guid类型的别名,并且还创建了自己的类型,称为Guid,具有匹配的构造函数接口 . 稍后,代码将创建Guid类型的实例 . 要创建此实例,编译器必须在Guid的两个不同定义之间进行选择 . 当using-alias指令放在namespace元素之外时,编译器将选择在本地名称空间中定义的Guid的本地定义,并完全忽略在名称空间外定义的using-alias指令 . 遗憾的是,这在阅读代码时并不明显 .

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

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

    在namespace元素中放置using-alias指令会将其作为bug的来源消除 .

    • 多个命名空间

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

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

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

  • 46

    在答案中讨论了技术原因,我认为最终涉及个人偏好,因为差异并不大,而且两者都存在权衡 . 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"
        }
      }
    }
    

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

  • 184

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

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

  • 31

    实际上存在(微妙的)差异两者之间 . 想象一下,你在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
        {
        }
    }
    

    编译器在查看命名空间外的那些 using 指令之前搜索 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;
            }
        }
    }
    

    现在编译器在搜索 Outer 之前搜索 System ,找到 System.Math ,一切都很好 .

    有些人认为 Math 可能是用户定义类的坏名称,因为 System 中已有一个;这里的重点只是存在差异,它会影响代码的可维护性 .

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

  • -8

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

    不漂亮 .

  • 364

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

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

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

    完全等同于:

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

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

    解析哪种类型的规则可以如下所示松散地说明: First search the inner-most "scope" for a match, if nothing is found there go out one level to the next scope and search there, and so on ,直到找到匹配为止 . 如果在某个级别找到多个匹配项,如果其中一个类型来自当前程序集,则选择该类型并发出编译器警告 . 否则,放弃(编译时错误) .

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

    (1) With usings outside:

    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命名空间中的类型(全局命名空间)
      00022 SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty 中的类型

    另一个惯例:

    (2) With usings inside:

    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

    SystemSystem.Collections.Generic0102243010244MyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty 中的类型

    • 命名空间中的类型 MyCorp.TheProduct.SomeModule

    • MyCorp 中的类型

    • null命名空间中的类型(全局命名空间)

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

    Concluding remarks

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

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

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

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

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

  • 1

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

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

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

    如果你的源解决方案中使用的“引用”应该在命名空间之外,并且那些 "new added reference" 是一个很好的做法,那么你应该将它放在命名空间中,这是一种更好的做法 . 这是为了区分什么正在添加引用 .

  • 0

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

    using ThisNamespace.IsImported.InAllNamespaces.Here;
    
    namespace Namespace1
    { 
       using ThisNamespace.IsImported.InNamespace1.AndNamespace2;
    
       namespace Namespace2
       { 
          using ThisNamespace.IsImported.InJustNamespace2;
       }       
    }
    
    namespace Namespace3
    { 
       using ThisNamespace.IsImported.InJustNamespace3;
    }
    

相关问题