c# - resharper feature




“使用”指令應該位於命名空間的內部還是外部? (6)

兩者之間實際存在著(微妙的)區別。 想像一下你在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成員,所以Outer.Math現在被破壞了。

如果你把你的名字空間聲明放在你的名字空間中,如下所示:

// 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的位置如何,在Outer.Math中添加Outer.Math中斷Outer.Math 。 這意味著編譯器在查看任何using指令之前搜索最內層的封閉名稱空間。

我一直在通過一些C#代碼運行StyleCop ,並且不斷報告我的using指令應該位於命名空間內。

using指令放在裡面而不是命名空間之外是否有技術原因?


如果使用源解決方案中使用的“ 引用 ”的默認值應該在命名空間之外,而那些“新增引用”是一個好習慣,那麼這是一個更好的做法,那就是你應該把它放在命名空間中。 這是為了區分正在添加的引用。



根據StyleCop文檔:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

原因AC#使用指令放置在命名空間元素之外。

規則說明如果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");的行上的以下編譯器錯誤Guid g = new Guid("hello");

CS0576:命名空間'Microsoft.Sample'包含與別名'Guid'衝突的定義

該代碼創建一個名為Guid的System.Guid類型的別名,並創建一個名為Guid的具有匹配構造函數接口的類型。 稍後,代碼將創建一個Guid類型的實例。 為了創建這個實例,編譯器必須在Guid的兩個不同定義之間進行選擇。 當using-alias指令放在名稱空間元素之外時,編譯器將選擇在本地名稱空間內定義的Guid的本地定義,並完全忽略在名稱空間外定義的using-alias指令。 不幸的是,這在閱讀代碼時並不明顯。

但是,當using-alias指令位於名稱空間內時,編譯器必須在兩個在相同名稱空間內定義的不同衝突Guid類型之間進行選擇。 這兩種類型都提供了一個匹配的構造函數。 編譯器無法做出決定,所以它會標記編譯器錯誤。

在命名空間之外放置using-alias指令是一種不好的做法,因為它可能導致在這種情況下的混淆,在這種情況下,哪種版本的類型實際上並不明顯。 這可能會導致可能難以診斷的錯誤。

在命名空間元素中放置使用別名指令可以消除這種錯誤。

  1. 多個命名空間

將多個命名空間元素放在單個文件中通常是一個糟糕的主意,但是如果並且完成後,最好將所有使用的指令放在每個名稱空間元素中,而不是全局在文件頂部。 這將嚴格限制名稱空間,並且還將有助於避免上述類型的行為。

值得注意的是,當使用放置在名稱空間之外的指令編寫代碼時,在命名空間內移動這些指令時應小心,以確保這不會改變代碼的語義。 如上所述,在名稱空間元素中放置使用別名指令允許編譯器以指令放置在名稱空間外部時不會發生的衝突類型之間進行選擇。

如何解決違規問題要解決違反此規則的問題,請在名稱空間元素內移動所有使用偽指令和使用別名偽指令。


當您希望使用別名時,在命名空間內放置使用語句存在問題。 該別名不受益於先前的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>>>;

不漂亮。


這個線程已經有了一些很好的答案,但我覺得我可以帶來更多的細節與這個額外的答案。

首先,請記住帶有句點的名稱空間聲明,如:

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

完全等同於:

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

如果你想,你可以在所有這些級別上using指令。 (當然,我們只想在一個地方using s,但根據語言它是合法的。)

解決這種類型隱含的規則可以這樣寬鬆地陳述: 首先搜索最內層的“範圍”進行匹配,如果沒有發現任何內容,則向下一個範圍出去一層並在那裡搜索,依此類推 ,直到找到一場比賽。 如果在某個級別找到多個匹配項,如果其中一個類型來自當前程序集,請選擇該項並發出編譯器警告。 否則,放棄(編譯時錯誤)。

現在讓我們明確一下這兩個主要公約的具體例子的含義。

(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. 名稱空間中的類型(全局名稱空間)
  7. 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按以下順序進行:

  1. C嵌套類型(包括繼承的嵌套類型)
  2. 當前命名空間MyCorp.TheProduct.SomeModule.Utilities類型
  3. System.Collections.GenericSystem.LinqMyCorp.TheProductMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.IntegrationThirdParty
  4. 命名空間MyCorp.TheProduct.SomeModule類型
  5. MyCorp類型
  6. 名稱空間中的類型(全局名稱空間)

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

結束語

無論將用途放在名稱空間聲明的內部還是外部,總會有人有可能在稍後向具有較高優先級的名稱空間之一添加一個名稱相同的新類型。

另外,如果嵌套名稱空間與類型名稱相同,則可能會導致問題。

將使用從一個位置移動到另一個位置是非常危險的,因為搜索層次結構會發生變化,並且可能會找到其他類型。 因此,選擇一個約定並堅持下去,這樣你就不必移動使用了。

默認情況下,Visual Studio的模板將使用情況放在命名空間之外 (例如,如果您讓VS在新文件中生成新類)。

外部使用的一個(微小)優點是,您可以使用全局屬性的using指令,例如[assembly: ComVisible(false)]而不是[assembly: System.Runtime.InteropServices.ComVisible(false)]





code-organization