web-dev-qa-db-ja.com

ディレクティブは名前空間の内側に置くべきですか、それとも外側に置くべきですか?

私はいくつかのC#コードで StyleCop を実行してきました、そしてそれは私のusing指令が名前空間の中にあるべきであると報告し続けます。

usingディレクティブを名前空間の外側ではなく内側に置くことに技術的な理由はありますか?

1888
benPearce

実際には両者の間に(微妙な)違いがあります。 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はユーザー定義クラスにとっては不適切な名前である可能性があると主張する人もいます。ここで重要な点は、の違いであり、コードの保守性に影響するということです。

FooOuter.Innerではなく名前空間Outerにある場合に何が起こるかに注意することも興味深いです。その場合、File2にOuter.Mathを追加すると、usingの場所に関係なくFile1が壊れます。これは、usingディレクティブを調べる前に、コンパイラーが最も内側の名前空間を検索することを意味します。

1971
Charlie

このスレッドには既にいくつかの素晴らしい回答がありますが、この追加の回答でもう少し詳しく説明できると思います。

まず、次のようなピリオドを含む名前空間宣言を覚えておいてください。

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

以下と完全に同等です:

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

必要に応じて、これらのすべてのレベルにusingディレクティブを配置できます。 (もちろん、1つの場所にusingsを配置したいだけですが、言語によっては合法です。)

暗示されるタイプを解決するためのルールは、このように大まかに述べることができます:最初に一致する最も内側の「スコープ」を検索します。など、一致が見つかるまで。あるレベルで複数の一致が見つかった場合、タイプの1つが現在のアセンブリからのものである場合、その1つを選択し、コンパイラ警告を発行します。それ以外の場合は、giveめます(コンパイル時エラー)。

次に、2つの主要な規則を使用した具体的な例で、これが何を意味するのかを明確にしましょう。

(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. SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.Integration、および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;
    }
}

次に、タイプAmbiguousの検索を次の順序で行います。

  1. C内のネストされたタイプ(継承されたネストされたタイプを含む)
  2. 現在のネームスペースの型MyCorp.TheProduct.SomeModule.Utilities
  3. SystemSystem.Collections.GenericSystem.LinqMyCorp.TheProductMyCorp.TheProduct.OtherModuleMyCorp.TheProduct.OtherModule.Integration、およびThirdPartyのタイプ
  4. 名前空間の型MyCorp.TheProduct.SomeModule
  5. MyCorpのタイプ
  6. null名前空間(グローバル名前空間)の型

MyCorp.TheProductは「3.」の一部であったため、「4。」と「5.」の間では不要であったことに注意してください。)

結語

名前空間宣言の内部または外部のどちらに使用を配置しても、誰かが後で優先順位の高い名前空間の1つに同じ名前の新しい型を追加する可能性が常にあります。

また、ネストされた名前空間が型と同じ名前を持つ場合、問題が発生する可能性があります。

検索階層が変更され、別のタイプが見つかる可能性があるため、ある場所から別の場所に使用を移動することは常に危険です。したがって、1つの規則を選択し、それに固執することで、これを使用して移動する必要がなくなります。

Visual Studioのテンプレートは、既定で、名前空間の使用outsideを配置します(たとえば、VSで新しいファイルに新しいクラスを生成する場合)。

Usingoutsideを使用する1つの(小さな)利点は、[Assembly: ComVisible(false)]の代わりに[Assembly: System.Runtime.InteropServices.ComVisible(false)]のように、グローバル属性のusingディレクティブを利用できることです。

412

名前空間の内側に配置すると、宣言はファイルのその名前空間に対してローカルになります(ファイル内に複数の名前空間がある場合)。ただし、名前空間がファイルごとに1つしかない場合、それらが外側にあるかどうかに大きな違いはありません。名前空間の中.

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
188
Mark Cidade

Hanselmanによると - 指令とアセンブリのロードを使用して... および他のそのような記事は技術的に違いはありません。

私の好みはそれらを名前空間の外に置くことです。

60

StyleCopのドキュメントによると:

SA1200:UsingDirectivesMustBePlacedWithinNamespace

原因C#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'と競合する定義が含まれています

このコードは、GuidというSystem.Guid型のエイリアスを作成し、さらに対応するコンストラクタインターフェイスを持つGuidという独自の型も作成します。その後、コードはGuid型のインスタンスを作成します。このインスタンスを作成するには、コンパイラは2つの異なるGuidの定義から選択する必要があります。 using-aliasディレクティブをnamespace要素の外側に置くと、コンパイラはローカルネームスペース内で定義されているGuidのローカル定義を選択し、ネームスペースの外側で定義されているusing-aliasディレクティブを完全に無視します。残念ながら、これはコードを読むときには明らかではありません。

しかし、using-aliasディレクティブがネームスペース内に配置されている場合、コンパイラは、同じネームスペース内で定義された2つの異なる競合するGuid型の中から選択する必要があります。これらの型はどちらも一致するコンストラクタを提供します。コンパイラは決定を下すことができないため、コンパイラエラーのフラグが立てられます。

Using-aliasディレクティブをネームスペースの外側に配置することは、実際に使用されている型のバージョンが明らかでない場合など、混乱を招く可能性があるため、不適切な方法です。これは潜在的に診断が難しいかもしれないバグを引き起こす可能性があります。

Using-aliasディレクティブをnamespace要素内に配置することで、これをバグの原因として排除できます。

  1. 複数のネームスペース

1つのファイル内に複数の名前空間要素を配置するのは一般的に悪い考えですが、これを行う場合は、すべてのusingディレクティブをファイルの先頭に配置するのではなく、各名前空間要素内に配置することをお勧めします。これは名前空間を厳密に範囲指定し、上記のような振る舞いを回避するのにも役立ちます。

コードが名前空間の外側に置かれたディレクティブを使って書かれているとき、これらのディレクティブを名前空間の中で動かすとき、これがコードの意味論を変えないことを確実にするために注意が払われるべきです。上で説明したように、namespace要素内にusing-aliasディレクティブを配置すると、コンパイラーは、ディレクティブが名前空間の外側に配置されていても起こらない方法で、矛盾する型を選択できます。

違反を修正する方法この規則の違反を修正するには、namespace要素内でusingディレクティブとusing-aliasディレクティブをすべて移動します。

47
JaredCacurak

エイリアスを使用したいときに、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>>>;

かわいくないです。

31
Neo

Jeppe Stig Nielsen 言われた として、このスレッドはすでにすばらしい答えを持っています、しかし私はこのかなり明白な微妙さもまた言及する価値があると思いました。

名前空間の内側で指定されたusingディレクティブは、外側で指定されたときのように完全修飾される必要がないため、コードを短くすることができます。

次の例は、Foo型とBar型が両方とも同じグローバル名前空間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;
    }
}
3
Biscuits

他の答えではカバーされていないと思われるもう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.
        }
    }
}
1
Ben Gardner

私が遭遇した1つのしわ(それは他の答えでカバーされない):

これらの名前空間があるとします。

  • 何か。その他
  • Parent.Something.Other

using Something.Othernamespace Parent outside を使うとき、それは最初のもの(Something.Other)を参照します。

ただし、その名前空間宣言の inside を使用すると、2番目の宣言(Parent.Something.Other)が参照されます。

簡単な解決策があります: "global::"接頭辞を追加してください: docs

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

技術的な理由は答えの中で議論されています、そしてそれは違いが big ではなくそしてそれらの両方のためにトレードオフがあるので私はそれが最後に個人的な好みに来ると思います。 .csファイルを作成するためのVisual Studioのデフォルトテンプレートは、名前空間の外側でusingディレクティブを使用します。

次のようにプロジェクトファイルのルートにstylecop.jsonファイルを追加することで、ネームスペースの外側でusingディレクティブをチェックするようにstylecopを調整できます。

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

この設定ファイルをソリューションレベルで作成し、「既存のリンクファイル」としてプロジェクトに追加して、すべてのプロジェクトで設定を共有することもできます。

0
sotn