web-dev-qa-db-ja.com

forループで宣言された変数はローカル変数ですか?

私はかなり長い間C#を使用してきましたが、次のことに気付きませんでした。

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

この名前の変数を宣言できない場合、forブロックの外側で 'i'の値を使用できないのはなぜですか?

Forループで使用される反復子変数は、そのスコープ内でのみ有効であると考えました。

133
John V

Forループとforループの両方で同じ名前の変数を定義できないのは、外部スコープの変数が内部スコープで有効であるためです。これが許可されている場合、forループ内に2つの「i」変数があることを意味します。

参照: MSDNスコープ

具体的には:

Local-variable-declaration(セクション8.5.1)で宣言されたローカル変数のスコープは、宣言が発生するブロックです。

そして

Forステートメント(セクション8.8.3)のfor-initializerで宣言されたローカル変数のスコープは、forステートメントのfor-initializer、for-condition、for-iterator、および含まれているステートメントです。

また、: ローカル変数宣言 (C#仕様のセクション8.5.1)

具体的には:

local-variable-declarationで宣言されたローカル変数のスコープは、宣言が発生するブロックです。ローカルを参照するのはエラーですローカル変数のローカル変数宣言子の前にあるテキスト位置の変数。 ローカル変数のスコープ内では、同じ名前の別のローカル変数または定数を宣言することはコンパイル時エラーです。

(エンファシス鉱山。)

つまり、forループ内のiのスコープはforループです。 forループ外のiのスコープは、メインメソッド全体plusforループです。つまり、ループ内でiが2回出現することになりますが、これは上記では無効です。

許可されない理由int A = i;int iは、forループ内での使用のみを対象としています。したがって、forループの外側ではアクセスできなくなりました。

ご覧のとおり、これらの問題は両方ともスコーピングの結果です。最初の問題(int i = 4;)は、iループスコープ内に2つのfor変数を生成します。一方、int A = i;は、スコープ外の変数にアクセスします。

代わりにできることは、iをメソッド全体にスコープするように宣言し、それをメソッドとforループスコープの両方で使用することです。これにより、どちらのルールも破られなくなります。

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

[〜#〜] edit [〜#〜]

もちろん、C#コンパイラを変更して、このコードを非常に有効にコンパイルできるようにすることもできます。結局これは有効です:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

しかし、次のようなコードを記述できることは、コードの可読性と保守性にとって本当に有益でしょうか?

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

ここで間違いの可能性について考えてください。最後のiは0または4を出力しますか?これは非常に小さな例であり、追跡と追跡は非常に簡単ですが、外側のiを別の名前で宣言した場合よりも保守性と可読性がはるかに低くなります。

N.B:

C#のスコープルールは C++のスコープルール とは異なることに注意してください。 C++では、変数は宣言された場所からブロックの終わりまでの範囲内にのみあります。これにより、コードがC++で有効な構成体になります。

119
Johannes Kommer

J.Kommerの答えは正しい:簡単に言うと、ローカル変数をローカル変数宣言スペースで宣言することは違法ですoverlaps同じ名前のローカルを持つ別のローカル変数宣言スペース.

ここでも違反されているC#の追加ルールがあります。追加の規則は、2つの異なる重複するローカル変数宣言スペース内の2つの異なるエンティティを参照するために単純な名前を使用することは違法であるということですあなたの例は違法です、これも違法です:

class C
{
    int x;
    void M()
    {
        int y = x;
        if(whatever)
        {
            int x = 123;

なぜなら、「this.x」とローカル「x」という2つの異なることを意味するために、「y」のローカル変数宣言スペース内で単純名「x」が使用されているためです。

これらの問題の詳細な分析については、 http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ を参照してください。

29
Eric Lippert

ループの後、メソッド内でiを宣言して使用する方法があります。

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

これはJava(Cから発生する可能性があるかどうかはわかりません)。

13
Chris S

J.Kommerの回答に加えて(+1 btw)。 NETスコープの標準にはこれがあります。

blockIfステートメントなどのブロック構造内で変数を宣言する場合、その変数のスコープはブロックの最後までです。ライフタイムは、プロシージャが終了するまでです。

Procedureプロシージャ内で、Ifステートメントの外で変数を宣言する場合、スコープはEnd SubまたはEnd Functionまでです。変数の有効期間は、プロシージャが終了するまでです。

したがって、forループヘッダー内でデカールされたint iは、forループブロック[〜#〜] but [〜#〜]の間のみスコープ内にあります。 Main()コードが完了するまで存続します。

7
ChrisBD

ibeforeforループを宣言した場合、ループ内で宣言することはまだ有効だと思いますか?

いいえ、2つのスコープが重複するためです。

できないことはint A=i;、それは単に、iforループにのみ存在するためです。

7
Widor

これについて考える最も簡単な方法は、Iの外側の宣言をループの上に移動することです。それはそれから明らかになるはずです。

どちらの場合も同じスコープなので、実行できません。

5
Andrew Barber

また、C#の規則は、厳密なプログラミングの観点からは多くの場合必要ありませんが、コードをクリーンで読みやすい状態に保つために存在します。

たとえば、ループの後に定義した場合は問題ありませんが、コードを読んで定義行を忘れた人は、ループの変数に関係していると考えるかもしれません。

4
Guy

Kommerの答えは技術的に正しいです。鮮やかなブラインドスクリーンのメタファーでそれを言い換えさせてください。

Forブロックと外側の外側のブロックの間には一方向のブラインドスクリーンがあり、forブロック内のコードは外側のコードを見ることができますが、外側のブロックのコードは内側のコードを見ることができません。

外部コードはinsideを認識できないため、内部で宣言されたものは使用できません。しかし、for-blockのコードはinsideとoutsideの両方を見ることができるため、両方の場所で宣言された変数を名前で明確に使用することはできません。

だから、あなたはそれを見ないか、あなたはC#です!

2
explorer

couldintブロックでusingを宣言する場合と同じように見てください:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

ただし、intIDisposableを実装しないため、これは実行できません。ただし、誰かがint変数をプライベートスコープに配置する方法を視覚化するのに役立ちます。

別の方法は言うことです、

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

これが何が起こっているかを視覚化するのに役立つことを願っています。

intループ内からforを宣言することで、コードが素晴らしく、タイトに保たれるため、この機能が本当に気に入っています。

0
jp2code