省略 - using system c#
'using'ディレクティブは名前空間の内部または外部にあるべきですか? (7)
私はStyleCopをいくつかのC#コードで実行していて、 using
ディレクティブが名前空間内にあることを報告し続けます。
名前空間の外ではなく、 using
ディレクティブを内部に置く技術的な理由はありますか?
Jeppe Stig Nielsenがsaid 、このスレッドは既に大きな答えを持っていますが、私はこの明らかな微妙さも言及する価値があると思いました。
名前空間内で指定されたディレクティブ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;
}
}
to StyleCopのドキュメントによると:
SA1200:UsingDirectivesMustBePlacedWithinNamespace
原因AC#usingディレクティブは、名前空間要素の外側に配置されています。
ルールの説明このルールに違反するのは、ファイルに名前空間要素が含まれていない限り、usingディレクティブまたはusing-aliasディレクティブがnamespace要素の外部に置かれている場合に発生します。
たとえば、次のコードはこのルールに2回違反します。
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の2つの異なる定義の中から選択する必要があります。 using-aliasディレクティブがnamespace要素の外部に置かれると、コンパイラはローカル名前空間内で定義されたGuidのローカル定義を選択し、名前空間の外で定義されたusing-aliasディレクティブを完全に無視します。 残念ながら、これはコードを読むときには分かりません。
ただし、using-aliasディレクティブが名前空間内に配置されている場合、コンパイラは、同じ名前空間内で定義された異なる2つのGuidタイプのいずれかを選択する必要があります。 これらの両方の型は、一致するコンストラクタを提供します。 コンパイラは決定を下すことができないため、コンパイラのエラーにフラグを立てます。
using-aliasディレクティブを名前空間の外側に置くことは悪いことですが、実際にどのバージョンの型が実際に使用されているのか分かりにくいこのような状況で混乱を招く可能性があるからです。 これにより、診断が困難なバグが発生する可能性があります。
namespace要素内でのエイリアスディレクティブの使用は、バグの原因としてこれを排除します。
- 複数の名前空間
複数の名前空間要素を1つのファイルに配置することは一般的には悪い考えですが、これが実行された場合は、すべてのディレクティブをファイルの先頭に配置するのではなく、各名前空間要素内に配置することをお勧めします。 これにより、名前空間が厳密に範囲指定され、上記のような動作を回避するのにも役立ちます。
ネームスペースの外部に配置されたディレクティブを使用してコードを記述した場合、これらのディレクティブをネームスペース内で移動してコードのセマンティクスを変更しないように注意する必要があります。 上で説明したように、namespace要素内でusing-aliasディレクティブを配置すると、コンパイラは、ディレクティブが名前空間の外側に配置された場合に発生しないような方法で競合するタイプを選択できます。
違反を修正するこのルールの違反を修正するには、namespace要素内のディレクティブとusing-aliasディレクティブをすべて使用して移動します。
このスレッドはすでにいくつかの素晴らしい答えを持っていますが、私はこの追加の答えでもう少し詳しく説明することができます。
まず、次のようなピリオドを含む名前空間宣言を覚えておいてください。
namespace MyCorp.TheProduct.SomeModule.Utilities
{
...
}
次のものと完全に同等です。
namespace MyCorp
{
namespace TheProduct
{
namespace SomeModule
{
namespace Utilities
{
...
}
}
}
}
必要ならば、これらすべてのレベルでディレクティブをusing
することができます。 (もちろん、sは1つの場所にしかusing
しませんが、言語によっては合法です。)
何かが暗示されているかどうかを判断するためのルールは、次のように緩やかに記述することができます。 最初に一致のために最も内側の「範囲」を検索します。何も見つからなければ、次の範囲に1レベル進み 、一致が見つかるまで あるレベルで複数の一致が見つかった場合、そのタイプの1つが現在のアセンブリからのものであれば、それを選択してコンパイラの警告を出します。 さもなければ、あきらめる(コンパイル時エラー)。
さて、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
タイプを調べるために、検索は次の順序で行われます。
-
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
、および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
型の検索は次の順序で行われます:
-
C
内のネストされた型(継承されたネストされた型を含む) - 現在の名前空間の型
MyCorp.TheProduct.SomeModule.Utilities
-
System
、System.Collections.Generic
、System.Linq
、MyCorp.TheProduct
、MyCorp.TheProduct.OtherModule
、MyCorp.TheProduct.OtherModule.Integration
、およびThirdParty
- 名前空間
MyCorp.TheProduct.SomeModule
-
MyCorp
タイプ - null名前空間の型(グローバル名前空間)
( MyCorp.TheProduct
は "3."の一部であったため、 "4."と "5."の間には必要ありませんでした)。
結論
あなたが名前空間宣言の内側か外側に使うかどうかにかかわらず、誰かが後に優先度の高い名前空間の1つに同じ名前の新しい型を追加する可能性が常にあります。
また、ネストされた名前空間が型と同じ名前を持つ場合、問題を引き起こす可能性があります。
検索階層が変更され、別のタイプが見つかる可能性があるため、ある場所から別の場所への使用を移動することは常に危険です。 したがって、1つのコンベンションを選択してそれに固執してください。そうすれば、使い方を動かす必要はありません。
Visual Studioのテンプレートは、既定では、名前空間の外側に使用を置きます(たとえば、VSを新しいファイルで新しいクラスを生成させる場合)。
外部を使用する利点の1つは、 [assembly: System.Runtime.InteropServices.ComVisible(false)]
ではなく[assembly: ComVisible(false)]
などのグローバル属性に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>>>;
綺麗ではない。
名前空間の中に入れておくと、宣言はファイルの名前空間のローカルになります(ファイルに複数の名前空間がある場合)。ファイルごとに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;
}
実際には2つの(微妙な)違いがあります。 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
検索し、 System.Math
代わりにOuter.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
はユーザ定義のクラスの悪い名前であるかもしれないと主張する人もいるでしょう。 ここでのポイントは 、違いがあり、コードの保守性に影響するということだけです。
また、 Foo
がOuter.Inner
ではなくOuter
名前空間にある場合に何が起こるか注意することも興味深い。 その場合、File2でOuter.Math
を追加すると、どこでusing
かかわらずFile1が中断されます。 これは、コンパイラがusing
ディレクティブを見る前に、最も内側の名前空間を検索することを意味します。
私が信じていない別の微妙な点は、同じ名前のクラスと名前空間があるときです。
名前空間内にインポートがあると、そのクラスが見つかります。 インポートが名前空間外にある場合、インポートは無視され、クラスと名前空間は完全修飾されていなければなりません。
//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.
}
}
}