次の方法で共有


7 基本的な概念

7.1 アプリケーションの起動

プログラムは、他のアプリケーションの一部としてクラス ライブラリとして、または直接開始できるアプリケーションとしてコンパイルできます。 このコンパイル モードを決定するメカニズムは、実装定義であり、この仕様の外部にあります。

アプリケーションとしてコンパイルされたプログラムには、次の要件を満たすことにより、エントリ ポイントとして修飾されるメソッドが少なくとも 1 つ含まれている必要があります。

  • Mainの名前を持つ必要があります。
  • static
  • 一般的なものではありません。
  • 非ジェネリック型で宣言する必要があります。 メソッドを宣言する型が入れ子になった型の場合、その外側の型のいずれもジェネリックではない可能性があります。
  • メソッドの戻り値の型がSystem.Threading.Tasks.TaskまたはSystem.Threading.Tasks.Task<int>されている場合は、async修飾子を持つことができます。
  • 戻り値の型は、 voidintSystem.Threading.Tasks.Task、または System.Threading.Tasks.Task<int>にする必要があります。
  • これは、実装のない部分メソッド (§15.6.9) でもありません。
  • パラメーター リストは空であるか、 string[]型の単一の値パラメーターを持つ必要があります。

: async 修飾子を持つメソッドは、エントリ ポイントとして修飾するために、上記で指定した 2 つの戻り値の型のいずれかである必要があります。 async voidメソッド、またはValueTaskValueTask<int>など、別の待機可能な型を返すasync メソッドは、エントリ ポイントとして修飾されません。 end note

プログラム内でエントリ ポイントとして修飾される複数のメソッドが宣言されている場合は、外部メカニズムを使用して、アプリケーションの実際のエントリ ポイントと見なされるメソッドを指定できます。 戻り値の型が int または void を持つ修飾メソッドが見つかった場合、戻り値の型が System.Threading.Tasks.Task または System.Threading.Tasks.Task<int> を持つ修飾メソッドは、エントリ ポイント メソッドとは見なされません。 これは、プログラムが 1 つのエントリ ポイントなしでアプリケーションとしてコンパイルされるコンパイル時エラーです。 クラス ライブラリとしてコンパイルされたプログラムには、アプリケーション エントリ ポイントとして修飾されるメソッドを含めることができますが、結果のライブラリにはエントリ ポイントがありません。

通常、メソッドの宣言されたアクセシビリティ (§7.5.2) は、その宣言で指定されたアクセス修飾子 (§15.3.6) によって決定されます。同様に、型の宣言されたアクセシビリティは、その宣言で指定されたアクセス修飾子によって決定されます。 特定の型の特定のメソッドを呼び出し可能にするには、型とメンバーの両方にアクセスできる必要があります。 ただし、アプリケーション エントリ ポイントは特殊なケースです。 具体的には、実行環境は、宣言されたアクセシビリティに関係なく、および外側の型宣言の宣言されたアクセシビリティに関係なく、アプリケーションのエントリ ポイントにアクセスできます。

エントリ ポイント メソッドの戻り値の型が System.Threading.Tasks.Task または System.Threading.Tasks.Task<int> の場合、コンパイラは、対応する Main メソッドを呼び出す同期エントリ ポイント メソッドを合成します。 合成されたメソッドには、 Main メソッドに基づくパラメーターと戻り値の型があります。

  • 合成メソッドのパラメーター リストは、 Main メソッドのパラメーター リストと同じです
  • Main メソッドの戻り値の型がSystem.Threading.Tasks.Task場合、合成されたメソッドの戻り値の型は次の値になります。void
  • Main メソッドの戻り値の型がSystem.Threading.Tasks.Task<int>場合、合成されたメソッドの戻り値の型は次の値になります。int

合成されたメソッドの実行は、次のように実行されます。

  • 合成されたメソッドは、Main メソッドを呼び出し、Main メソッドにこのようなパラメーターがある場合は、そのstring[]パラメーター値を引数として渡します。
  • Main メソッドが例外をスローすると、合成されたメソッドによって例外が伝達されます。
  • それ以外の場合、合成されたエントリ ポイントは、パラメーターなしのインスタンス メソッドまたは §C.3 で記述された拡張メソッドを使用して、タスクのGetAwaiter().GetResult()を呼び出して、返されたタスクが完了するまで待機。 タスクが失敗した場合、 GetResult() は例外をスローし、この例外は合成メソッドによって伝達されます。
  • 戻り値の型がSystem.Threading.Tasks.Task<int>Main メソッドの場合、タスクが正常に完了すると、GetResult()によって返されたint値が合成メソッドから返されます。

アプリケーションの 効果のないエントリ ポイント は、プログラム内で宣言されたエントリ ポイント、または前述のように必要な場合は合成メソッドです。 したがって、有効なエントリ ポイントの戻り値の型は常に void または int

アプリケーションを実行すると、新しい アプリケーション ドメイン が作成されます。 アプリケーションの複数の異なるインスタンス化が同じコンピューター上に同時に存在する場合があり、それぞれに独自のアプリケーション ドメインがあります。 アプリケーション ドメインでは、アプリケーションの状態のコンテナーとして機能することで、アプリケーションを分離できます。 アプリケーション ドメインは、アプリケーションで定義されている型と、それを使用するクラス ライブラリのコンテナーと境界として機能します。 1 つのアプリケーション ドメインに読み込まれる型は、別のアプリケーション ドメインに読み込まれる同じ型とは異なり、オブジェクトのインスタンスはアプリケーション ドメイン間で直接共有されません。 たとえば、各アプリケーション ドメインには、これらの型の静的変数の独自のコピーがあり、型の静的コンストラクターは、アプリケーション ドメインごとに最大で 1 回実行されます。 実装では、アプリケーション ドメインを作成および破棄するための実装定義のポリシーまたはメカニズムを自由に提供できます。

アプリケーションの起動は、実行環境がアプリケーションの有効なエントリ ポイントを呼び出すときに発生します。 有効なエントリ ポイントがパラメーターを宣言する場合、アプリケーションの起動時に、実装では、そのパラメーターの初期値が文字列配列への null 以外の参照であることを確認する必要があります。 この配列は、アプリケーションの起動前にホスト環境によって実装定義値が与えられる、 アプリケーション パラメーターと呼ばれる、null 以外の文字列への参照で構成されます。 目的は、ホスト環境の他の場所からアプリケーションを起動する前に決定されたアプリケーション情報を提供することです。

: コマンド・ラインをサポートするシステムでは、アプリケーション・パラメーターは一般にコマンド・ライン引数と呼ばれるものに対応します。 end note

有効なエントリ ポイントの戻り値の型が int場合、実行環境によるメソッド呼び出しからの戻り値がアプリケーションの終了で使用されます (§7.2)。

上記の状況以外に、エントリ ポイント メソッドは、すべての点でエントリ ポイントではないような動作をします。 特に、通常のメソッド呼び出しなど、アプリケーションの有効期間中にエントリ ポイントが他のポイントで呼び出された場合、メソッドの特別な処理はありません。パラメーターがある場合は、 nullの初期値、または null 参照を含む配列を参照するnull 以外の値を持つことができます。 同様に、エントリ ポイントの戻り値には、実行環境からの呼び出し以外に特別な意味はありません。

7.2 アプリケーションの終了

アプリケーションの終了 は実行環境に制御を返します。

アプリケーションの有効なエントリ ポイント メソッドの戻り値の型が int され、例外が発生せずに実行が完了した場合、返される int の値は、アプリケーションの 支配状態コードとして機能します。 このコードの目的は、実行環境への成功または失敗の通信を許可することです。 有効なエントリ ポイント メソッドの戻り値の型が void され、例外が発生せずに実行が完了した場合、終了状態コードは 0

例外 (§21.4 が原因で有効なエントリ ポイント メソッドが終了した場合、終了コードは実装で定義されます。 さらに、この実装では、終了コードを指定するための代替 API が提供される場合があります。

ファイナライザー (§15.13) をアプリケーション終了の一部として実行するかどうかは、実装で定義されます。

: .NET Framework の実装では、クリーンアップが抑制されていない限り、ガベージ コレクションされていないすべてのオブジェクトに対してファイナライザー (§15.13) を呼び出すあらゆる妥当な努力が行われます (たとえば、ライブラリ メソッド GC.SuppressFinalizeの呼び出しによって)。 end note

7.3 宣言

C# プログラムの宣言は、プログラムの構成要素を定義します。 C# プログラムは名前空間を使用して編成されます。 これらは、型宣言と入れ子になった名前空間宣言を含むことができる名前空間宣言 (§14) を使用して導入されます。 型宣言 (§14.7) は、クラス (§15)、構造体 (§16)、インターフェイス (§18)、列挙型 (§19)、デリゲート (§20) を定義するために使用されます。 型宣言で許可されるメンバーの種類は、型宣言の形式によって異なります。 たとえば、 クラス宣言には、定数 (§15.4)、フィールド (§15.5)、メソッド (§15. 6)、プロパティ (§15.7)、イベント (§15.8)、インデクサー (§15.15 9)、演算子 (§15.10)、インスタンス コンストラクター (§15.11)、静的コンストラクター (§15.12)、ファイナライザー (§15.13)、入れ子になった型 (§15.3.9)。

宣言は、宣言が属する 宣言スペース 内の名前を定義します。 次の場合を除き、宣言空間に同じ名前のメンバーを導入する 2 つ以上の宣言があると、コンパイル時エラーになります。

  • 同じ宣言空間では、同じ名前の 2 つ以上の名前空間宣言を使用できます。 このような名前空間宣言は、1 つの論理名前空間を形成し、1 つの宣言空間を共有するために集計されます。
  • 別のプログラムの宣言が、同じ名前空間宣言空間内にある場合は、同じ名前を共有できます。

    : ただし、これらの宣言では、同じアプリケーションに含まれている場合、あいまいさが生じる可能性があります。 end note

  • 同じ名前の異なるシグネチャを持つ 2 つ以上のメソッドは、同じ宣言空間で使用できます (§7.6)。
  • 同じ名前で異なる型パラメーターの番号を持つ 2 つ以上の型宣言は、同じ宣言空間で使用できます (§7.8.2)。
  • 同じ宣言空間に部分修飾子を持つ 2 つ以上の型宣言は、同じ名前、同じ数の型パラメーター、および同じ分類 (クラス、構造体、またはインターフェイス) を共有できます。 この場合、型宣言は単一の型に寄与し、それ自体が集約されて単一の宣言空間を形成します (§15.2.7)。
  • 同じ宣言空間内の名前空間宣言と型宣言は、型宣言に少なくとも 1 つの型パラメーター (§7.8.2) がある限り、同じ名前を共有できます。

次に示すように、宣言スペースにはさまざまな種類があります。

  • プログラムのすべてのコンパイル単位内で、namespace_declarationを囲まないnamespace_member_declarationは、グローバル宣言空間と呼ばれる単一の結合宣言空間のメンバー
  • プログラムのすべてのコンパイル単位内で、同じ完全修飾名前空間名を持つnamespace_declaration内のnamespace_member_declarationは、単一の結合宣言スペースのメンバーです。
  • compilation_unitnamespace_body には、 エイリアス宣言スペースがありますcompilation_unitまたはnamespace_bodyの各extern_alias_directiveusing_alias_directiveは、エイリアス宣言空間 (§14.5.2) にメンバーを提供します。
  • 各非部分クラス、構造体、またはインターフェイス宣言は、新しい宣言空間を作成します。 各部分クラス、構造体、またはインターフェイス宣言は、同じプログラム内のすべての一致する部分によって共有される宣言空間に寄与します (§16.2.4)。 名前は、 class_member_declarationstruct_member_declarationinterface_member_declaration、または type_parameterによって、この宣言空間に導入されます。 オーバーロードされたインスタンス コンストラクター宣言と静的コンストラクター宣言を除き、クラスまたは構造体には、クラスまたは構造体と同じ名前のメンバー宣言を含めることはできません。 クラス、構造体、またはインターフェイスは、オーバーロードされたメソッドとインデクサーの宣言を許可します。 さらに、クラスまたは構造体は、オーバーロードされたインスタンス コンストラクターと演算子の宣言を許可します。 たとえば、クラス、構造体、またはインターフェイスには、同じ名前の複数のメソッド宣言を含めることができます。これらのメソッド宣言のシグネチャが異なる場合 (§7.6)。 基底クラスはクラスの宣言空間に寄与せず、基本インターフェイスはインターフェイスの宣言空間に寄与しないことに注意してください。 したがって、派生クラスまたはインターフェイスは、継承されたメンバーと同じ名前のメンバーを宣言できます。 このようなメンバーは、継承されたメンバー表示されると言います。
  • 各デリゲート宣言は、新しい宣言空間を作成します。 この宣言空間には、パラメーター (fixed_parameterparameter_array) と type_parameterによって名前が導入されます。
  • 各列挙宣言は、新しい宣言空間を作成します。 名前は、 enum_member_declarationsを通じてこの宣言空間に導入されます。
  • 各メソッド宣言、プロパティ宣言、プロパティ アクセサー宣言、インデクサー宣言、インデクサー アクセサー宣言、演算子宣言、インスタンス コンストラクター宣言、匿名関数、ローカル関数は、 ローカル変数宣言空間と呼ばれる新しい宣言空間を作成。 この宣言空間には、パラメーター (fixed_parameterparameter_array) と type_parameterによって名前が導入されます。 プロパティまたはインデクサーの set アクセサーは、パラメーターとして value 名前を導入します。 関数メンバー、匿名関数、またはローカル関数の本体 (存在する場合) は、ローカル変数宣言空間内で入れ子になっていると見なされます。 ローカル変数宣言スペースと入れ子になったローカル変数宣言スペースに、入れ子になったローカル名のスコープ内に同じ名前の要素が含まれている場合、外側のローカル名は入れ子になったローカル名によって非表示になります (§7.7.1)。
  • 追加のローカル変数宣言スペースは、メンバー宣言、匿名関数、およびローカル関数内で発生する可能性があります。 これらの宣言スペースには、 pattern、declaration_expressiondeclaration_statementexception_specifierを使用して名前が導入されます。 ローカル変数宣言スペースは入れ子にすることができますが、ローカル変数宣言スペースと入れ子になったローカル変数宣言スペースに同じ名前の要素が含まれている場合はエラーになります。 したがって、入れ子になった宣言空間内では、ローカル変数、ローカル関数、または定数を、外側の宣言空間でパラメーター、型パラメーター、ローカル変数、ローカル関数、または定数と同じ名前で宣言することはできません。 宣言領域に他方が含まれていない限り、2 つの宣言領域に、同じ名前の要素を含めることができます。 ローカル宣言スペースは、次のコンストラクトによって作成されます。
    • フィールドとプロパティ宣言の各 variable_initializer は、他のローカル変数宣言空間内に入れ子になっていない独自のローカル変数宣言空間を導入します。
    • 関数メンバー、匿名関数、またはローカル関数の本体 (存在する場合) は、関数のローカル変数宣言空間内で入れ子になっていると見なされるローカル変数宣言空間を作成します。
    • constructor_initializer は、インスタンス コンストラクター宣言内に入れ子になったローカル変数宣言スペースを作成します。 コンストラクター本体のローカル変数宣言空間は、このローカル変数宣言空間内に入れ子になっています。
    • blockswitch_blockspecific_catch_clauseiteration_statementusing_statement は、入れ子になったローカル変数宣言スペースを作成します。
    • statement_listの直接の一部ではない各embedded_statementは、入れ子になったローカル変数宣言スペースを作成します。
    • switch_section は、入れ子になったローカル変数宣言スペースを作成します。 ただし、switch_sectionstatement_list内で直接宣言された変数 (ただし、statement_list内の入れ子になったローカル変数宣言空間内にはない) は、switch_sectionではなく、外側のswitch_blockのローカル変数宣言空間に直接追加されます。
    • query_expression (§12.20.3) の構文翻訳では、1 つ以上のラムダ式が導入される場合があります。 これらの各匿名関数は、前述のようにローカル変数宣言空間を作成します。
  • ブロック または switch_block は、ラベル用に個別の宣言スペースを作成します。 名前は labeled_statementを介してこの宣言空間に導入され、名前は goto_statementを介して参照されます。 ブロックの ラベル宣言スペース には、入れ子になったブロックが含まれます。 したがって、入れ子になったブロック内では、外側のブロック内のラベルと同じ名前のラベルを宣言することはできません。

: switch_sectionではなく、switch_section内で直接宣言された変数がswitch_blockのローカル変数宣言空間に追加されるという事実は、驚くべきコードにつながる可能性があります。 次の例では、 y ローカル変数は、ケース 0 の switch セクションに宣言が表示されているにもかかわらず、既定のケースの switch セクション内のスコープ内にあります。 ローカル変数 z は、宣言が行われる switch セクションのローカル変数宣言スペースに導入されているため、既定のケースの switch セクション内のスコープ内にありません。

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

end note

名前が宣言されるテキストの順序は、一般に意味がありません。 特に、テキストの順序は、名前空間、定数、メソッド、プロパティ、イベント、インデクサー、演算子、インスタンス コンストラクター、ファイナライザー、静的コンストラクター、および型の宣言と使用にとって重要ではありません。 宣言の順序は、次の点で重要です。

  • フィールド宣言の宣言順序によって、初期化子 (ある場合) が実行される順序が決まります (§15.5.6.2, §15.5.6.3)。
  • ローカル変数は、使用する前に定義する必要があります (§7.7)。
  • 列挙型メンバー宣言 (§19.4) の宣言順序は、 constant_expression 値を省略した場合に重要です。

: 名前空間の宣言空間は "open ended" であり、完全修飾名が同じ 2 つの名前空間宣言が同じ宣言空間に寄与します。 次に例を示します。

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

上記の 2 つの名前空間宣言は、同じ宣言空間に影響します。この場合、完全修飾名が Megacorp.Data.Customer および Megacorp.Data.Orderで 2 つのクラスを宣言します。 2 つの宣言は同じ宣言空間に影響するため、それぞれが同じ名前のクラスの宣言を含む場合、コンパイル時エラーが発生しました。

end の例

: 上記で指定したように、ブロックの宣言スペースには入れ子になったブロックが含まれます。 したがって、次の例では、 F メソッドと G メソッドは、名前 i が外側のブロックで宣言されており、内部ブロックで再宣言できないため、コンパイル時エラーが発生します。 ただし、2 つのiは入れ子になっていない個別のブロックで宣言されているため、HメソッドとI メソッドは有効です。

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

end note

7.4 メンバー

7.4.1 全般

名前空間と型には members があります。

: エンティティのメンバーは、エンティティへの参照から始まり、その後に "." トークンが続き、その後にメンバーの名前が続く修飾名を使用して一般提供されます。 end note

型のメンバーは、型宣言で宣言されるか、型の基底クラスから inherited 宣言されます。 型が基底クラスから継承されると、インスタンス コンストラクター、ファイナライザー、静的コンストラクターを除く基底クラスのすべてのメンバーが派生型のメンバーになります。 基底クラス メンバーの宣言されたアクセシビリティは、メンバーが継承されるかどうかを制御しません。継承は、インスタンス コンストラクター、静的コンストラクター、またはファイナライザーではないメンバーまで拡張されます。

: ただし、継承されたメンバーは、宣言されたアクセシビリティ (§7.5.2) のため、派生型ではアクセスできない場合があります。 end note

7.4.2 名前空間メンバー

外側の名前空間を持たない名前空間と型は、 グローバル名前空間のメンバーです。 これは、グローバル宣言空間で宣言された名前に直接対応します。

名前空間内で宣言された名前空間と型は、その名前空間のメンバーです。 これは、名前空間の宣言空間で宣言された名前に直接対応します。

名前空間には、アクセス制限がありません。 プライベート、保護、または内部の名前空間を宣言することはできません。名前空間名は常にパブリックにアクセスできます。

7.4.3 構造体メンバー

構造体のメンバーは構造体で宣言されたメンバーであり、構造体の直接基底クラス System.ValueType と間接基底クラスから継承されたメンバー object

単純型のメンバーは、単純型 (§8.3.5) によってエイリアス化された構造体型のメンバーに直接対応します。

7.4.4 列挙メンバー

列挙体のメンバーは、列挙型で宣言された定数と、列挙型の直接基底クラス System.Enum から継承されたメンバー、および間接基底クラスの System.ValueTypeobjectです。

7.4.5 クラス メンバー

クラスのメンバーは、クラスで宣言されたメンバーと基底クラスから継承されたメンバーです (基底クラスを持たないクラス object を除きます)。 基底クラスから継承されるメンバーには、基底クラスの定数、フィールド、メソッド、プロパティ、イベント、インデクサー、演算子、型が含まれますが、基底クラスのインスタンス コンストラクター、ファイナライザー、静的コンストラクターは含まれません。 基底クラスのメンバーは、アクセシビリティに関係なく継承されます。

クラス宣言には、定数、フィールド、メソッド、プロパティ、イベント、インデクサー、演算子、インスタンス コンストラクター、ファイナライザー、静的コンストラクター、および型の宣言が含まれる場合があります。

object (§8.2.3) およびstring (§8.2.5) のメンバーは、エイリアスのクラス型のメンバーに直接対応します。

7.4.6 インターフェイス メンバー

インターフェイスのメンバーは、インターフェイスとインターフェイスのすべての基本インターフェイスで宣言されたメンバーです。

: クラス object のメンバーは、厳密には、どのインターフェイス (§18.4) のメンバーでありません。 ただし、クラス object のメンバーは、任意のインターフェイス型 (§12.5) のメンバー参照を介して使用できます。 end note

7.4.7 配列メンバー

配列のメンバーは、クラス System.Arrayから継承されたメンバーです。

7.4.8 メンバーの委任

デリゲートは、クラス System.Delegateからメンバーを継承します。 さらに、宣言で指定されたのと同じ戻り値の型とパラメーター リストを持つ Invoke という名前のメソッドが含まれています (§20.2)。 このメソッドの呼び出しは、同じデリゲート インスタンスでのデリゲート呼び出し (§20.6) と同じように動作します。

実装では、継承を通じて、またはデリゲート自体で直接、追加のメンバーを提供できます。

7.5 メンバーアクセス

7.5.1 全般

メンバーの宣言では、メンバー アクセスを制御できます。 メンバーのアクセシビリティは、メンバーの宣言されたアクセシビリティ (§7.5.2) と、すぐに含まれる型のアクセシビリティ (存在する場合) によって確立されます。

特定のメンバーへのアクセスが許可されている場合、そのメンバーは アクセス可能であると言。 逆に、特定のメンバーへのアクセスが許可されていない場合、メンバーは アクセスできないと言われます。 メンバーへのアクセスは、アクセスが行われるテキストの場所がメンバーのアクセシビリティ ドメイン (§7.5.3) に含まれている場合に許可されます。

7.5.2 宣言されたアクセシビリティ

メンバーの 宣言されたアクセシビリティ は、次のいずれかになります。

  • Public。メンバー宣言に public 修飾子を含めることで選択されます。 publicの直感的な意味は、"アクセスに制限されない" です。
  • Protected。メンバー宣言に protected 修飾子を含めることで選択されます。 protectedの直感的な意味は、"包含クラスまたは包含クラスから派生した型に制限されるアクセス" です。
  • internal。メンバー宣言に internal 修飾子を含めることで選択されます。 internalの直感的な意味は、"アクセスがこのアセンブリに限定される" です。
  • 保護された内部。 protectedinternal 修飾子の両方をメンバー宣言に含めることで選択されます。 protected internalの直感的な意味は、「このアセンブリ内でアクセスできるだけでなく、包含クラスから派生した型」です。
  • プライベート保護。これは、メンバー宣言に private 修飾子と protected 修飾子の両方を含めることで選択されます。 private protectedの直感的な意味は、「包含クラスと、包含クラスから派生した型によって、このアセンブリ内でアクセスできる」ということです。
  • private。メンバー宣言に private 修飾子を含めることで選択されます。 privateの直感的な意味は、"access limited to the containing type"です。

メンバー宣言が行われるコンテキストに応じて、特定の種類の宣言されたアクセシビリティのみが許可されます。 さらに、メンバー宣言にアクセス修飾子が含まれていない場合、宣言が行われるコンテキストによって、宣言された既定のアクセシビリティが決まります。

  • 名前空間には、 public 宣言されたアクセシビリティが暗黙的に含まれています。 名前空間宣言ではアクセス修飾子は使用できません。
  • コンパイル 単位または名前空間で直接宣言された型 (他の型内ではなく) は、 public または internal 宣言されたアクセシビリティを持ち、既定で宣言されたアクセシビリティ internal 持つことができます。
  • クラス メンバーは、許可されている任意の種類の宣言されたアクセシビリティを持ち、既定で宣言されたアクセシビリティ private できます。

    : クラスのメンバーとして宣言された型には、許可されている任意の種類の宣言されたアクセシビリティを持つことができますが、名前空間のメンバーとして宣言された型は、 public または宣言 internal アクセシビリティのみを持つことができます。 end note

  • 構造体メンバーは、宣言されたアクセシビリティ publicinternal、または private を持つ場合があり、構造体は暗黙的にシールされるため、既定で宣言されたアクセシビリティを private できます。 structで導入された構造体メンバー (つまり、その構造体によって継承されない) は、宣言されたアクセシビリティprotectedprotected internal、またはprivate protectedすることはできません。

    : 構造体のメンバーとして宣言された型は、 publicinternal、または private 宣言されたアクセシビリティを持つことができますが、名前空間のメンバーとして宣言された型は、 public または宣言 internal アクセシビリティのみを持つことができます。 end note

  • インターフェイス メンバーには、 public 宣言されたアクセシビリティが暗黙的に含まれます。 インターフェイス メンバー宣言では、アクセス修飾子は使用できません。
  • 列挙メンバーは、 public 宣言されたアクセシビリティを暗黙的に持ちます。 列挙メンバー宣言では、アクセス修飾子は使用できません。

7.5.3 アクセシビリティ ドメイン

メンバーの アクセス可能性ドメイン は、メンバーへのアクセスが許可されるプログラム テキストの (場合によっては不整合な) セクションで構成されます。 メンバーのアクセシビリティ ドメインを定義するために、メンバーが型内で宣言されていない場合はメンバーが top-level と言われ、メンバーが別の型内で宣言されている場合はnestedと言われます。 さらに、プログラムの プログラム テキスト は、プログラムのすべてのコンパイル単位に含まれるすべてのテキストとして定義され、型のプログラム テキストは、その型の type_declarationに含まれるすべてのテキストとして定義されます (場合によっては、型内で入れ子になっている型を含む)。

定義済みの型 ( objectintdoubleなど) のアクセシビリティ ドメインは無制限です。

プログラム Pで宣言されている最上位の非連結型T (§8.4.4) のアクセシビリティ ドメインは、次のように定義されます。

  • Tの宣言されたアクセシビリティがパブリックである場合、Tのアクセシビリティ ドメインは、Pのプログラム テキストと、Pを参照するすべてのプログラムです。
  • Tの宣言されたアクセシビリティが内部の場合、Tのアクセシビリティ ドメインはPのプログラム テキストです。

: これらの定義から、最上位の非連結型のアクセシビリティ ドメインは常に、少なくともその型が宣言されているプログラムのプログラム テキストに従います。 end note

構築された型 T<A₁, ..., Aₑ> のアクセシビリティ ドメインは、バインドされていないジェネリック型 T のアクセシビリティ ドメインと、 A₁, ..., Aₑ型引数のアクセシビリティ ドメインの共通部分です。

入れ子になったメンバーMプログラム P内の型Tで宣言されているアクセシビリティ ドメインは、次のように定義されます (M自体が型である可能性があることに注意してください)。

  • M に対して宣言されたアクセシビリティが public の場合、M のアクセシビリティ ドメインは T のアクセシビリティ ドメインになります。
  • Mの宣言されたアクセシビリティがprotected internal場合は、DPのプログラム テキストと、Pの外部で宣言されているTから派生した任意の型のプログラム テキストの和集合にします。 Mのアクセシビリティ ドメインは、DTのアクセシビリティ ドメインの共通部分です。
  • Mの宣言されたアクセシビリティがprivate protected場合は、Pのプログラム テキストとTのプログラム テキストと、Tから派生した任意の型の積集合Dします。 Mのアクセシビリティ ドメインは、DTのアクセシビリティ ドメインの共通部分です。
  • Mの宣言されたアクセシビリティがprotected場合は、DTのプログラム テキストと、Tから派生した任意の型のプログラム テキストの和集合にします。 Mのアクセシビリティ ドメインは、DTのアクセシビリティ ドメインの共通部分です。
  • M に対して宣言されているアクセシビリティが internal の場合、M のアクセシビリティ ドメインは、T のアクセシビリティ ドメインと P のプログラム テキストとの積集合になります。
  • M に対して宣言されているアクセシビリティが private の場合、M のアクセシビリティ ドメインは T のプログラム テキストになります。

: これらの定義から、入れ子になったメンバーのアクセシビリティ ドメインは常に、少なくともメンバーが宣言されている型のプログラム テキストである必要があります。 さらに、メンバーのアクセシビリティ ドメインが、メンバーが宣言されている型のアクセシビリティ ドメインよりも包括的になることはありません。 end note

: 直感的に言えば、型またはメンバーの M にアクセスすると、アクセスが許可されていることを確認するために次の手順が評価されます。

  • まず、 M が (コンパイル単位や名前空間ではなく) 型内で宣言されている場合、その型にアクセスできない場合、コンパイル時エラーが発生します。
  • その後、 Mpublicされている場合は、アクセスが許可されます。
  • それ以外の場合、 Mprotected internalされている場合は、 M が宣言されているプログラム内で発生した場合、または M が宣言され、派生クラス型 (§7.5.4 を介して実行されるクラスから派生したクラス内で発生した場合)、アクセスが許可されます。
  • それ以外の場合、 Mprotectedされている場合、アクセスは、 M が宣言されているクラス内で発生した場合、または M が宣言され、派生クラス型 (§7.5.4 を介して実行されるクラスから派生したクラス内で発生する場合)、アクセスが許可されます。
  • それ以外の場合、 Minternalされている場合は、 M が宣言されているプログラム内でアクセスが行われると、アクセスが許可されます。
  • それ以外の場合、 Mprivateされている場合は、 M が宣言されている型内でアクセスが行われると、アクセスが許可されます。
  • それ以外の場合は、型またはメンバーにアクセスできなくなり、コンパイル時エラーが発生します。 end note

: 次のコード内

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

クラスとメンバーには、次のアクセシビリティ ドメインがあります。

  • AA.Xのアクセシビリティ ドメインは無制限です。
  • A.YBB.XB.YB.CB.C.XB.C.Yのアクセシビリティ ドメインは、含まれているプログラムのプログラム テキストです。
  • A.Zのアクセシビリティ ドメインは、Aのプログラム テキストです。
  • B.ZB.Dのアクセシビリティ ドメインは、B.CB.Dのプログラム テキストを含む、Bのプログラム テキストです。
  • B.C.Zのアクセシビリティ ドメインは、B.Cのプログラム テキストです。
  • B.D.XB.D.Yのアクセシビリティ ドメインは、B.CB.Dのプログラム テキストを含む、Bのプログラム テキストです。
  • B.D.Zのアクセシビリティ ドメインは、B.Dのプログラム テキストです。 この例が示すように、メンバーのアクセシビリティ ドメインは、包含型のアクセシビリティ ドメインよりも大きくはありません。 たとえば、すべての X メンバーがパブリックに宣言されたアクセシビリティを持っている場合でも、 A.X 以外はすべて、包含型によって制約されるアクセシビリティ ドメインを持ちます。

end の例

§7.4 で説明されているように、インスタンス コンストラクター、ファイナライザー、静的コンストラクターを除く基底クラスのすべてのメンバーは、派生型によって継承されます。 これには、基底クラスのプライベート メンバーも含まれます。 ただし、プライベート メンバーのアクセシビリティ ドメインには、メンバーが宣言されている型のプログラム テキストのみが含まれます。

: 次のコード内

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

B クラスは、A クラスからプライベート メンバー xを継承します。 メンバーはプライベートであるため、Aclass_body内でのみアクセスできます。 したがって、 b.x へのアクセスは A.F メソッドでは成功しますが、 B.F メソッドでは失敗します。

end の例

7.5.4 保護されたアクセス

protectedまたはprivate protectedインスタンス メンバーが、それが宣言されているクラスのプログラム テキストの外部にアクセスされ、protected internal インスタンス メンバーが宣言されているプログラムのプログラム テキストの外部にアクセスされると、そのメンバーが宣言されているクラスから派生したクラス宣言内でアクセスが行われます。 さらに、その派生クラス型のインスタンスそこから構築されたクラス型行うためにアクセスが必要です。 この制限により、メンバーが同じ基底クラスから継承されている場合でも、1 つの派生クラスが他の派生クラスの保護されたメンバーにアクセスできなくなります。

B保護されたインスタンス メンバー Mを宣言する基底クラスにし、Bから派生するクラスDできるようにします。 Dclass_body内で、Mにアクセスするには、次のいずれかの形式を使用できます。

  • フォーム Mの修飾されていないtype_nameまたはprimary_expression
  • フォームE.Mprimary_expressionEの型がTまたはTから派生したクラスである場合、Tはクラス D、またはDから構築されたクラス型です。
  • フォーム base.Mprimary_expression
  • フォーム base[argument_list]primary_expression

これらの形式のアクセスに加えて、派生クラスは、 constructor_initializer (§15.11.2) の基底クラスの保護されたインスタンス コンストラクターにアクセスできます。

: 次のコード内

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

A内では、ABの両方のインスタンスを介してxにアクセスできます。どちらの場合も、アクセスは経由で行われるのでAのインスタンスまたはAから派生したクラスです。 ただし、B内では、ABから派生しないため、Aのインスタンスを介してxにアクセスすることはできません。

end の例

例:

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

ここでは、ジェネリック型から構築されたクラス型のインスタンスを介して行われるため、 x への 3 つの割り当てが許可されます。

end の例

: ジェネリック クラスで宣言されている保護されたメンバーのアクセシビリティ ドメイン (§7.5.3) には、そのジェネリック クラスから構築されたすべての型から派生したすべてのクラス宣言のプログラム テキストが含まれています。 この例では次のとおりです。

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

Dprotectedメンバー C<int>.xへの参照は、クラスDC<string>から派生している場合でも有効です。 end note

7.5.5 アクセシビリティの制約

C# 言語のいくつかのコンストラクトでは、少なくともメンバーまたは別の型と同じくらいアクセス可能な型が必要です。 T型は、Tのアクセシビリティ ドメインがMのアクセシビリティ ドメインのスーパーセットである場合、少なくともメンバーまたは型Mとしてアクセス可能であると言われます。 言い換えると、Tは、Mがアクセス可能なすべてのコンテキストでTにアクセスできる場合、少なくともMと同じくらいアクセスできます。

次のアクセシビリティ制約が存在します。

  • クラス型の直接基底クラスは、少なくともクラス型自体と同じくらいアクセス可能である必要があります。
  • インターフェイス型の明示的な基本インターフェイスは、少なくともインターフェイス型自体と同じくらいアクセス可能である必要があります。
  • デリゲート型の戻り値の型とパラメーター型は、少なくともデリゲート型自体と同じくらいアクセス可能である必要があります。
  • 定数の型は、少なくとも定数自体と同じくらいアクセス可能である必要があります。
  • フィールドの型は、少なくともフィールド自体と同じくらいアクセス可能である必要があります。
  • メソッドの戻り値の型とパラメーター型は、少なくともメソッド自体と同じくらいアクセス可能である必要があります。
  • プロパティの型は、少なくともプロパティ自体と同じくらいアクセス可能である必要があります。
  • イベントの種類は、少なくともイベント自体と同じくらいアクセス可能である必要があります。
  • インデクサーの型とパラメーターの型は、少なくともインデクサー自体と同じくらいアクセス可能である必要があります。
  • 演算子の戻り値の型とパラメーター型は、少なくとも演算子自体と同じくらいアクセス可能である必要があります。
  • インスタンス コンストラクターのパラメーター型は、少なくともインスタンス コンストラクター自体と同じくらいアクセス可能である必要があります。
  • 型パラメーターのインターフェイスまたはクラス型の制約は、少なくとも制約を宣言するメンバーと同じくらいアクセス可能である必要があります。

: 次のコード内

class A {...}
public class B: A {...}

B クラスでは、Aが少なくともBほどアクセスできないため、コンパイル時エラーが発生します。

end の例

: 同様に、次のコードに示します。

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

戻り値の型Aがメソッドほどアクセスできないため、BH メソッドはコンパイル時エラーになります。

end の例

7.6 署名とオーバーロード

メソッド、インスタンス コンストラクター、インデクサー、および演算子は、 署名によって特徴付けられます:

  • メソッドのシグネチャは、メソッドの名前、型パラメーターの数、および各パラメーターの型とパラメーターの受け渡しモードで構成され、左から右の順序で考慮されます。 このような場合、パラメーターの型で発生するメソッドの型パラメーターは、名前ではなく、メソッドの型パラメーター リスト内の序数位置によって識別されます。 メソッドのシグネチャには、戻り値の型、パラメーター名、型パラメーター名、型パラメーターの制約、 params または this パラメーター修飾子、パラメーターが必須か省略可能かは特に含まれません。
  • インスタンス コンストラクターのシグネチャは、各パラメーターの型とパラメーター受け渡しモードで構成され、左から右の順序で考慮されます。 インスタンス コンストラクターのシグネチャには、特に、右端のパラメーターに指定できる params 修飾子も、パラメーターが必須か省略可能かは含まれません。
  • インデクサーのシグネチャは、左から右の順序で考慮される各パラメーターの型で構成されます。 インデクサーのシグネチャには、特に要素型は含まれません。また、右端のパラメーターに指定できる params 修飾子も含まれません。また、パラメーターが必須か省略可能かは含まれません。
  • 演算子のシグネチャは、演算子の名前と各パラメーターの型で構成され、左から右の順序で考慮されます。 演算子のシグネチャには、結果の型は特に含まれません。
  • 変換演算子のシグネチャは、ソース型とターゲット型で構成されます。 変換演算子の暗黙的または明示的な分類は、シグネチャの一部ではありません。
  • 同じメンバーの種類 (メソッド、インスタンス コンストラクター、インデクサー、または演算子) の 2 つのシグネチャは、同じ名前、型パラメーターの数、パラメーターの数、パラメーターの受け渡しモードを持ち対応するパラメーターの型間に ID 変換が存在する場合は、と見なされます (§10.2.2)。

シグネチャは、クラス、構造体、およびインターフェイスのメンバーの オーバーローディング を有効にするメカニズムです。

  • メソッドをオーバーロードすると、クラス、構造体、またはインターフェイスのシグネチャがそのクラス、構造体、またはインターフェイス内で一意である場合に、同じ名前で複数のメソッドを宣言できます。
  • インスタンス コンストラクターのオーバーロードでは、クラスまたは構造体のシグネチャがそのクラスまたは構造体内で一意である場合に、複数のインスタンス コンストラクターを宣言できます。
  • インデクサーのオーバーロードでは、クラス、構造体、またはインターフェイスのシグネチャがそのクラス、構造体、またはインターフェイス内で一意であれば、複数のインデクサーを宣言できます。
  • 演算子をオーバーロードすると、クラスまたは構造体のシグネチャがそのクラスまたは構造体内で一意であれば、同じ名前で複数の演算子を宣言できます。

inout、およびrefパラメーター修飾子はシグネチャの一部と見なされますが、1 つの型で宣言されたメンバーは、inout、およびrefによってのみシグネチャで異なることはできません。 outまたはin修飾子を持つ両方のメソッドのすべてのパラメーターがref修飾子に変更された場合、シグネチャを持つ同じ型で 2 つのメンバーが宣言されている場合、コンパイル時エラーが発生します。 署名の照合 (非表示やオーバーライドなど) の他の目的では、 inout、および ref は署名の一部と見なされ、相互に一致しません。

: この制限は、C# プログラムを共通言語インフラストラクチャ (CLI) で簡単に実行できるようにすることです。これは、 inout、および refでのみ異なるメソッドを定義する方法を提供しません。 end note

object型とdynamic型は、署名を比較するときに区別されません。 したがって、 objectdynamic に置き換えるだけでシグネチャが異なる 1 つの型で宣言されたメンバーは許可されません。

: 次の例は、オーバーロードされた一連のメソッド宣言とそのシグネチャを示しています。

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

inout、およびrefパラメーター修飾子 (§15.6.2) はシグネチャの一部であることに注意してください。 したがって、 F(int)F(in int)F(out int) 、および F(ref int) はすべて一意の署名です。 ただし、 F(in int)F(out int) 、および F(ref int) は、シグネチャが inout、および refによってのみ異なるため、同じインターフェイス内で宣言することはできません。 また、戻り値の型と params 修飾子はシグネチャの一部ではないため、戻り値の型または params 修飾子の包含または除外のみに基づいてオーバーロードすることはできません。 そのため、上記で識別 F(int) メソッドの宣言と F(params string[]) 、コンパイル時エラーが発生します。 end の例

7.7 スコープ

7.7.1 全般

名前の スコープ は、名前の修飾なしで名前で宣言されたエンティティを参照できるプログラム テキストの領域です。 スコープは nestedすることができ、内部スコープは外部スコープから名前の意味を再宣言できます。 (ただし、入れ子になったブロック内でローカル変数またはローカル定数と同じ名前のローカル変数またはローカル定数を外側のブロックで宣言できないという§7.3 によって課される制限は削除されません)。その後、外部スコープの名前は、内部スコープの対象となるプログラム テキストの領域でhiddenと言われ、外部名へのアクセスは名前を修飾することによってのみ可能です。

  • namespace_declarationを囲まないnamespace_member_declaration (§14.6) によって宣言された名前空間メンバーのスコープは、プログラム テキスト全体です。

  • 完全修飾名がNnamespace_declaration内のnamespace_member_declarationによって宣言された名前空間メンバーのスコープは、完全修飾名がNまたはNで始まり、ピリオドが続くすべてのnamespace_declarationnamespace_bodyです。

  • extern_alias_directive (§14.4) によって定義された名前のスコープは、compilation_unitまたはnamespace_bodyをすぐに含むusing_directiveglobal_attributes、およびnamespace_member_declarationにわたって拡張されます。 extern_alias_directiveは、基になる宣言空間に新しいメンバーを提供しません。 つまり、 extern_alias_directive は推移的ではなく、発生する compilation_unit または namespace_body にのみ影響します。

  • using_directive (§14.5) によって定義またはインポートされた名前のスコープは、using_directiveが発生するcompilation_unitまたはnamespace_bodyglobal_attributesnamespace_member_declarationにわたって拡張されます。 using_directiveは、特定のcompilation_unitまたはnamespace_body内で 0 個以上の名前空間または型名を使用できますが、基になる宣言空間に新しいメンバーを提供しません。 つまり、 using_directive は推移的ではなく、発生する compilation_unit または namespace_body にのみ影響します。

  • class_declaration (§15.2) のtype_parameter_listによって宣言された型パラメーターのスコープは、そのclass_declarationclass_basetype_parameter_constraints_clauses、およびclass_bodyです。

    : クラスのメンバーとは異なり、このスコープは派生クラスには拡張されません。 end note

  • struct_declaration (§16.2) のtype_parameter_listによって宣言された型パラメーターのスコープは、そのstruct_declarationstruct_interfacestype_parameter_constraints_clause、およびstruct_bodyです。

  • interface_declaration (§18.2) のtype_parameter_listによって宣言された型パラメーターのスコープは、そのinterface_declarationinterface_basetype_parameter_constraints_clause、およびinterface_bodyです。

  • delegate_declaration (§20.2) のtype_parameter_listによって宣言された型パラメーターのスコープは、そのdelegate_declarationreturn_typeparameter_list、およびtype_parameter_constraints_clauseです。

  • method_declaration (§15.6.1) のtype_parameter_listによって宣言された型パラメーターのスコープは、method_declarationです。

  • class_member_declaration (§15.3.1) によって宣言されたメンバーのスコープは、宣言が行われるclass_bodyです。 さらに、クラス メンバーのスコープは、メンバーのアクセシビリティ ドメイン (§7.5.3) に含まれる派生クラスのclass_bodyまで拡張されます。

  • struct_member_declaration (§16.3) によって宣言されたメンバーのスコープは、宣言が行われるstruct_bodyです。

  • enum_member_declaration (§19.4) によって宣言されたメンバーのスコープは、宣言が行われるenum_bodyです。

  • method_declaration (§15.6) で宣言されたパラメーターのスコープは、そのmethod_declarationmethod_bodyまたはref_method_bodyです。

  • indexer_declaration (§15.9) で宣言されたパラメーターのスコープは、そのindexer_declarationindexer_bodyです。

  • operator_declaration (§15.10) で宣言されたパラメーターのスコープは、そのoperator_declarationoperator_bodyです。

  • constructor_declaration (§15.11) で宣言されたパラメーターのスコープは、そのconstructor_declarationconstructor_initializerブロックです。

  • lambda_expression (§12.19) で宣言されたパラメーターのスコープは、そのlambda_expressionlambda_expression_bodyです。

  • anonymous_method_expression (§12.19) で宣言されたパラメーターのスコープは、そのanonymous_method_expressionブロックです。

  • labeled_statement (§13.5) で宣言されたラベルのスコープは、宣言が行われるブロックです。

  • local_variable_declaration (§13.6.2) で宣言されたローカル変数のスコープは、宣言が行われるブロックです。

  • switch ステートメント (§13.8.3) のswitch_blockで宣言されたローカル変数のスコープは、switch_blockです。

  • for ステートメント (§13.9.4) のfor_initializerで宣言されたローカル変数のスコープは、for ステートメントのfor_initializerfor_conditionfor_iterator、およびembedded_statementです。

  • local_constant_declaration (§13.6.3) で宣言されたローカル定数のスコープは、宣言が行われるブロックです。 constant_declaratorの前にあるテキスト位置のローカル定数を参照するのはコンパイル時エラーです。

  • foreach_statementusing_statementlock_statement、またはquery_expressionの一部として宣言された変数のスコープは、指定されたコンストラクトの拡張によって決まります。

名前空間、クラス、構造体、または列挙メンバーのスコープ内では、メンバーの宣言の前にあるテキスト位置でメンバーを参照できます。

例:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

ここでは、 F が宣言される前に i を参照することが有効です。

end の例

ローカル変数のスコープ内では、宣言子の前のテキスト位置でローカル変数を参照するのはコンパイル時エラーです。

例:

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

上記の F メソッドでは、 i への最初の割り当ては、外側のスコープで宣言されたフィールドを特に参照していません。 代わりに、ローカル変数を参照し、変数の宣言の前にテキストで記述されるため、コンパイル時エラーが発生します。 G メソッドでは、jの宣言に対する初期化子でのjの使用は、宣言子の前にないため有効です。 H メソッドでは、後続の宣言子は、同じlocal_variable_declaration内の以前の宣言子で宣言されたローカル変数を正しく参照します。

end の例

: ローカル変数とローカル定数のスコープ規則は、式コンテキストで使用される名前の意味がブロック内で常に同じであることを保証するように設計されています。 ローカル変数のスコープが宣言からブロックの末尾までしか拡張されなかった場合、上記の例では、最初の代入がインスタンス変数に割り当てられ、2 番目の代入がローカル変数に割り当てられ、ブロックのステートメントが後で再配置された場合にコンパイル時エラーが発生する可能性があります)。

ブロック内の名前の意味は、名前が使用されるコンテキストによって異なる場合があります。 この例では、

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

A名は、ローカル変数Aを参照するために式コンテキストで使用され、クラスAを参照する型コンテキストで使用されます。

end note

7.7.2 名前の非表示

7.7.2.1 全般

通常、エンティティのスコープには、エンティティの宣言空間よりも多くのプログラム テキストが含まれます。 特に、エンティティのスコープには、同じ名前のエンティティを含む新しい宣言スペースを導入する宣言が含まれる場合があります。 このような宣言により、元のエンティティが hiddenになります。 逆に、エンティティが非表示でない場合は表示可能であると言われます。

名前の非表示は、入れ子によってスコープが重なり合い、スコープが継承によって重複する場合に発生します。 次のサブクラウスでは、2 種類の非表示の特性について説明します。

7.7.2.2 入れ子で隠す

名前の入れ子による非表示は、名前空間または名前空間内の型を入れ子にした結果、クラスまたは構造体内の型を入れ子にした結果、ローカル関数またはラムダの結果として、およびパラメーター、ローカル変数、およびローカル定数宣言の結果として発生する可能性があります。

: 次のコード内

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

Fメソッド内では、インスタンス変数iはローカル変数iによって非表示になりますが、Gメソッド内では、インスタンス変数を参照i。 ローカル関数 M1 内では、 float i はすぐに外側の iを非表示にします。 ラムダ パラメーター i は、ラムダ本体内の float i を非表示にします。

end の例

内部スコープ内の名前が外部スコープ内の名前を非表示にすると、その名前のオーバーロードされたすべての出現箇所が非表示になります。

: 次のコード内

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

F(1)呼び出しは、Innerで宣言されたFを呼び出します。これは、Fの外側のすべての出現が内部宣言によって隠されるためです。 同じ理由で、 F("Hello") 呼び出しの結果、コンパイル時エラーが発生します。

end の例

7.7.2.3 継承による非表示

継承による名前の非表示は、基底クラスから継承された名前をクラスまたは構造体が再宣言するときに発生します。 この種類の名前の非表示には、次のいずれかの形式が使用されます。

  • クラスまたは構造体で導入された定数、フィールド、プロパティ、イベント、または型は、同じ名前のすべての基底クラス メンバーを非表示にします。
  • クラスまたは構造体で導入されたメソッドは、同じ名前を持つすべてのメソッド以外の基底クラス メンバーと、同じシグネチャを持つすべての基底クラス メソッドを非表示にします (§7.6)。
  • クラスまたは構造体で導入されたインデクサーは、同じシグネチャ (§7.6) を持つすべての基底クラス インデクサーを非表示にします。

演算子宣言 (§15.10) を管理する規則により、派生クラスで基底クラスの演算子と同じシグネチャを持つ演算子を宣言できなくなります。 したがって、演算子は互いを隠すことはありません。

外部スコープから名前を非表示にするのとは対照的に、継承されたスコープから表示されている名前を非表示にすると、警告が報告されます。

: 次のコード内

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

DerivedでのFの宣言により、警告が報告されます。 継承された名前を非表示にすることは、基底クラスの個別の進化を妨げるので、特にエラーではありません。 たとえば、 Base の新しいバージョンで、以前のバージョンのクラスに存在しない F メソッドが導入されたため、上記の状況が発生した可能性があります。

end の例

継承された名前を非表示にすることによって発生する警告は、 new 修飾子を使用して排除できます。

例:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

new修飾子は、DerivedFが "new" であり、継承されたメンバーを非表示にすることを意図していることを示します。

end の例

新しいメンバーの宣言では、継承されたメンバーは、新しいメンバーのスコープ内でのみ非表示になります。

例:

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

上記の例では、DerivedF宣言では、Baseから継承されたFが非表示になりますが、Derivedの新しいFにはプライベート アクセスがあるため、そのスコープはMoreDerivedまで拡張されません。 したがって、MoreDerived.Gの呼び出しF()は有効であり、Base.Fを呼び出します。

end の例

7.8 名前空間と型名

7.8.1 全般

C# プログラム内のいくつかのコンテキストでは、 namespace_name または type_name を指定する必要があります。

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;
    
namespace_or_type_name
    : identifier type_argument_list?
    | namespace_or_type_name '.' identifier type_argument_list?
    | qualified_alias_member
    ;

namespace_nameは、名前空間を参照するnamespace_or_type_nameです。

以下で説明するように、namespace_namenamespace_or_type_nameは名前空間を参照するか、それ以外の場合はコンパイル時エラーが発生します。 namespace_nameには型引数 (§8.4.2) は存在できません (型引数を持つ型のみ)。

type_nameは、型を参照するnamespace_or_type_nameです。 次に説明する解決策に従って、type_namenamespace_or_type_nameは型を参照する必要があります。それ以外の場合はコンパイル時エラーが発生します。

namespace_or_type_namequalified_alias_memberの場合、その意味は §14.8.1 で説明。 それ以外の場合、 namespace_or_type_name には次の 4 つの形式のいずれかが含まれます。

  • I
  • I<A₁, ..., Aₓ>
  • N.I
  • N.I<A₁, ..., Aₓ>

ここで、 I は単一の識別子であり、 Nnamespace_or_type_name であり、 <A₁, ..., Aₓ> は省略可能な type_argument_listです。 type_argument_listが指定されていない場合は、xを 0 にすることを検討してください。

namespace_or_type_nameの意味は次のように決定されます。

  • namespace_or_type_namequalified_alias_memberの場合、意味は §14.8.1 で指定
  • それ以外の場合、 namespace_or_type_name がフォーム I またはフォームの I<A₁, ..., Aₓ>の場合:
    • xが 0 で、namespace_or_type_nameがジェネリック メソッド宣言 (§15.6) 内に出現し、attributes の外部にある場合method-header,の宣言に名前Iを持つ型パラメーター (§15.2.3) が含まれている場合、 その後、namespace_or_type_nameはその型パラメーターを参照します。
    • それ以外の場合、 namespace_or_type_name が型宣言内に出現する場合は、各インスタンス型 T (§15.3.2) に対して、その型宣言のインスタンス型から始まり、外側にある各クラスまたは構造体宣言のインスタンス型 (存在する場合) を続行します。
      • xが 0 で、Tの宣言に名前Iを持つ型パラメーターが含まれている場合、namespace_or_type_nameはその型パラメーターを参照します。
      • それ以外の場合、 namespace_or_type_name が型宣言の本体内に表示され、 T またはその基本型に名前 I および x 型パラメーターを持つ入れ子になったアクセス可能な型が含まれている場合、 namespace_or_type_name は、指定された型引数で構築されたその型を参照します。 このような型が複数存在する場合は、より多くの派生型内で宣言された型が選択されます。

      : 型パラメーターの数が異なる型メンバー (定数、フィールド、メソッド、プロパティ、インデクサー、演算子、インスタンス コンストラクター、ファイナライザー、および静的コンストラクター) と型メンバーは、 namespace_or_type_nameの意味を決定するときに無視されます。 end note

    • それ以外の場合は、名前空間 Nごとに、 namespace_or_type_name が発生する名前空間から始まり、外側の各名前空間 (存在する場合) を続行し、グローバル名前空間で終わると、エンティティが見つかるまで次の手順が評価されます。
      • xが 0 で、IN 内の名前空間の名前である場合は、次のようになります。
        • namespace_or_type_nameが発生する場所がNの名前空間宣言で囲まれており、名前空間宣言に名前Iを名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、namespace_or_type_nameはあいまいになり、コンパイル時エラーが発生します。
        • それ以外の場合、namespace_or_type_nameNI という名前の名前空間を参照します。
      • それ以外の場合、 N に名前 Ix 型パラメーターを持つアクセス可能な型が含まれている場合は、次のようになります。
        • xが 0 で、namespace_or_type_nameが発生する場所がNの名前空間宣言で囲まれており、名前空間宣言に名前Iを名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、namespace_or_type_nameはあいまいになり、コンパイル時エラーが発生します。
        • それ以外の場合、 namespace_or_type_name は、指定された型引数で構築された型を参照します。
      • それ以外の場合、 namespace_or_type_name が発生する場所が Nの名前空間宣言で囲まれている場合:
        • xが 0 で、名前空間宣言に、名前Iをインポートされた名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、namespace_or_type_nameはその名前空間または型を参照します。
        • それ以外の場合、名前空間宣言の using_namespace_directiveによってインポートされた名前空間に、名前 Ix 型パラメーターを持つ型が 1 つだけ含まれている場合、 namespace_or_type_name は、指定された型引数で構築されたその型を参照します。
        • それ以外の場合、名前空間宣言の using_namespace_directiveによってインポートされた名前空間に、名前 I および x 型パラメーターを持つ複数の型が含まれている場合、 namespace_or_type_name はあいまいになり、エラーが発生します。
    • それ以外の場合、 namespace_or_type_name は未定義であり、コンパイル時エラーが発生します。
  • それ以外の場合、 namespace_or_type_name はフォーム N.I またはフォーム N.I<A₁, ..., Aₓ>です。 N は、最初に namespace_or_type_nameとして解決されます。 Nの解決が成功しなかった場合は、コンパイル時エラーが発生します。 それ以外の場合、 N.I または N.I<A₁, ..., Aₓ> は次のように解決されます。
    • xがゼロで、Nが名前空間を参照し、Nに名前がI入れ子になった名前空間が含まれている場合、namespace_or_type_nameはその入れ子になった名前空間を参照します。
    • それ以外の場合、 N が名前空間を参照し、 N に名前 Ix 型パラメーターを持つアクセス可能な型が含まれている場合、 namespace_or_type_name は、指定された型引数で構築されたその型を参照します。
    • それ以外の場合、 N (構築されている可能性がある) クラスまたは構造体型を参照し、 N またはその基底クラスに名前 I および x 型パラメーターを持つ入れ子になったアクセス可能な型が含まれている場合、 namespace_or_type_name は、指定された型引数で構築されたその型を参照します。 このような型が複数存在する場合は、より多くの派生型内で宣言された型が選択されます。

      : Nの基底クラス仕様の解決の一環としてN.Iの意味が決定される場合、Nの直接基底クラスはobjectと見なされます (§15.2.4.2)。 end note

    • それ以外の場合、 N.I は無効な namespace_or_type_nameであり、コンパイル時エラーが発生します。

namespace_or_type_nameは、静的クラス (§15.2.2.4) を参照する場合にのみ許可されます。

  • namespace_or_type_nameは、フォーム T.Inamespace_or_type_name内のTです。
  • namespace_or_type_nameは、フォームのtypeof_expression (§12.8.17) のTです。typeof(T)

7.8.2 修飾されていない名前

すべての名前空間宣言と型宣言には、 修飾名 次のように決定されます。

  • 名前空間宣言の場合、非修飾名は宣言で指定された qualified_identifier です。
  • type_parameter_listのない型宣言の場合、非修飾名は宣言で指定されたidentifierです。
  • K 型パラメーターを使用した型宣言の場合、非修飾名は宣言で指定された identifier 、その後に K 型パラメーターの generic_dimension_specifier (§12.8.17) が続きます。

7.8.3 完全修飾名

すべての名前空間と型宣言には、 完全に修飾された名前 、プログラム内の他のすべての名前空間または型宣言を一意に識別します。 N非修飾名を持つ名前空間または型宣言の完全修飾名は、次のように決定されます。

  • Nがグローバル名前空間のメンバーである場合、その完全修飾名はN
  • それ以外の場合は、その完全修飾名が S.Nされます。ここで、 S は、 N が宣言されている名前空間または型宣言の完全修飾名です。

つまり、Nの完全修飾名は、グローバル名前空間から始まる、Nにつながる識別子とgeneric_dimension_specifierの完全な階層パスです。 名前空間または型のすべてのメンバーは一意の名前を持つ必要があるため、名前空間または型宣言の完全修飾名は常に一意である必要があります。 2 つの異なるエンティティを参照するのは、同じ完全修飾名のコンパイル時エラーです。 特に次の点に違いがあります。

  • 名前空間宣言と型宣言の両方が同じ完全修飾名を持つことはエラーです。
  • 2 種類の型宣言が同じ完全修飾名を持つことはエラーです (たとえば、構造体とクラス宣言の両方に同じ完全修飾名がある場合)。
  • 部分修飾子のない型宣言が別の型宣言と同じ完全修飾名を持つことはエラーです (§15.2.7)。

: 次の例は、いくつかの名前空間と型宣言と、関連する完全修飾名を示しています。

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {           
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {         
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

end の例

7.9 自動メモリ管理

C# では、自動メモリ管理が採用されています。これにより、開発者はオブジェクトによって占有されているメモリを手動で割り当てたり解放したりできなくなります。 自動メモリ管理ポリシーは、ガベージ コレクターによって実装されます。 オブジェクトのメモリ管理のライフ サイクルは次のとおりです。

  1. オブジェクトが作成されると、メモリが割り当てられ、コンストラクターが実行され、オブジェクトは live と見なされます。
  2. ファイナライザーの実行を除き、実行可能な実行の継続によってオブジェクトとそのインスタンス フィールドにアクセスできない場合、オブジェクトは 使用されなくなったと見なされ ファイナライズの対象になります。

    : C# コンパイラとガベージ コレクターは、将来使用される可能性のあるオブジェクトへの参照を決定するためにコードを分析することを選択する場合があります。 たとえば、スコープ内にあるローカル変数がオブジェクトへの唯一の既存の参照であるが、そのローカル変数がプロシージャ内の現在の実行ポイントからの実行の継続で参照されることがない場合、ガベージ コレクターはオブジェクトを使用しなくなったものとして扱う可能性があります (ただし、必要ありません)。 end note

  3. オブジェクトがファイナライズの対象になると、後で指定されていない時点で、オブジェクトのファイナライザー (§15.13) (存在する場合) が実行されます。 通常の状況では、オブジェクトのファイナライザーは 1 回だけ実行されますが、実装定義 API ではこの動作をオーバーライドできます。
  4. オブジェクトのファイナライザーが実行されると、ファイナライザーの実行を含め、実行の継続によってオブジェクトもインスタンス フィールドにもアクセスできない場合、オブジェクトはアクセス不可と見なされ、オブジェクトはコレクションの対象になります。

    : 以前はアクセスできなかったオブジェクトは、ファイナライザーによって再びアクセス可能になる可能性があります。 この例を次に示します。 end note

  5. 最後に、オブジェクトがコレクションの対象になると、ガベージ コレクターはそのオブジェクトに関連付けられているメモリを解放します。

ガベージ コレクターはオブジェクトの使用状況に関する情報を保持し、この情報を使用して、新しく作成されたオブジェクトの場所、オブジェクトの再配置のタイミング、オブジェクトが使用されなくなったりアクセスできなくなったりする場合など、メモリ管理の決定を行います。

ガベージ コレクターが存在することを前提とする他の言語と同様に、C# は、ガベージ コレクターが幅広いメモリ管理ポリシーを実装できるように設計されています。 C# では、そのスパン内の時間制約も、ファイナライザーが実行される順序も指定しません。 ファイナライザーをアプリケーション終了の一部として実行するかどうかは、実装定義 (§7.2)。

ガベージ コレクターの動作は、クラス System.GCの静的メソッドを使用して、ある程度制御できます。 このクラスを使用して、コレクションの実行、ファイナライザーの実行 (または実行しない) などを要求できます。

: ガベージ コレクターは、オブジェクトを収集してファイナライザーを実行するタイミングを決定する際に広い緯度が許可されるため、準拠した実装では、次のコードとは異なる出力が生成される場合があります。 プログラム

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B? b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

は、クラス A のインスタンスとクラス Bのインスタンスを作成します。 これらのオブジェクトは、変数 b に値 nullが割り当てられるとガベージ コレクションの対象になります。この後、ユーザーが記述したコードがアクセスできなくなるためです。 出力は次のいずれかになります。

Finalize instance of A
Finalize instance of B

または

Finalize instance of B
Finalize instance of A

これは、オブジェクトがガベージ コレクションされる順序に制約がないためです。

微妙なケースでは、"最終処理の対象" と "コレクションの対象" の区別が重要になる場合があります。 たとえば、 にします。

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A? Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref?.F();
    }
}

class Test
{
    public static A? RefA;
    public static B? RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

上記のプログラムでは、ガベージ コレクターがBのファイナライザーの前にAのファイナライザーを実行することを選択した場合、このプログラムの出力は次のようになります。

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Aのインスタンスが使用されておらず、Aのファイナライザーが実行されましたが、Aのメソッド (この場合はF) を別のファイナライザーから呼び出す可能性があることに注意してください。 また、ファイナライザーを実行すると、メインライン プログラムからオブジェクトが再び使用できるようになる可能性があることに注意してください。 この場合、 Bのファイナライザーを実行すると、以前は使用されなかった A のインスタンスがライブ参照 Test.RefAからアクセスできるようになります。 WaitForPendingFinalizersの呼び出し後、Bのインスタンスはコレクションの対象となりますが、参照Test.RefAのため、Aのインスタンスは収集できません。

end の例

7.10 実行順序

C# プログラムの実行は、実行中の各スレッドの副作用が重要な実行ポイントで保持されるように続行されます。 側の効果は、揮発性フィールドの読み取りまたは書き込み、不揮発性変数への書き込み、外部リソースへの書き込み、および例外のスローとして定義されます。 これらの副作用の順序を保持する重要な実行ポイントは、揮発性フィールド (§15.5.4)、 lock ステートメント (§13.13)、およびスレッドの作成と終了への参照です。 実行環境は、次の制約に従って、C# プログラムの実行順序を自由に変更できます。

  • データ依存は、実行スレッド内で保持されます。 つまり、各変数の値は、スレッド内のすべてのステートメントが元のプログラム順序で実行されたかのように計算されます。
  • 初期化順序付け規則は保持されます (§15.5.5§15.5.6)。
  • 副作用の順序は、揮発性の読み取りと書き込みに関して保持されます (§15.5.4)。 さらに、その式の値が使用されておらず、必要な副作用が生成されないことを推測できる場合は、実行環境で式の一部を評価する必要はありません (メソッドの呼び出しや揮発性フィールドへのアクセスによって発生したものも含まれます)。 非同期イベント (別のスレッドによってスローされた例外など) によってプログラムの実行が中断された場合、監視可能な副作用が元のプログラムの順序で表示される保証はありません。