web-dev-qa-db-ja.com

C#変数のスコープ: 'x'は 'x'に異なる意味を与えるため、このスコープでは宣言できません

if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

これは次の結果になります:

エラー1 'var'という名前のローカル変数はこのスコープで宣言できません。'var 'には別の意味を与えるため、'子 'スコープで既に使用されています。

地球は本当に粉々に砕けていませんが、これは単なる間違いではありませんか?仲間の開発者と私は、最初の宣言を別のスコープにする必要があるかどうか疑問に思っていたため、2番目の宣言が最初の宣言を妨げることはありません。

C#が2つのスコープを区別できないのはなぜですか?最初のIFスコープは、メソッドの残りの部分から完全に分離されるべきではありませんか?

Ifの外部からvarを呼び出すことはできません。最初のvarが2番目のスコープに関連しないため、エラーメッセージは間違っています。

66
user1144

ここでの問題は、主に良い習慣であり、不注意によるミスを防ぐことの1つです。確かに、C#コンパイラ理論的には可能は、ここでスコープ間で競合が発生しないように設計されています。しかし、これは私が見るように、ほとんど利益を得られないほどの努力です。

親スコープでのvarの宣言がbefore ifステートメントである場合、解決できない名前の競合があることを考慮してください。コンパイラは、単に次の2つのケースを区別しません。分析は完了しました純粋にスコープに基づいてであり、予想どおりの宣言/使用の順序ではありません。

理論的には受け入れられます(ただし、C#に関しては無効です)。

if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

受け入れられない(親変数を非表示にするため):

string var = "New VAR!";

if(true)
{
    string var = "VAR";
}

変数とスコープの点でどちらも同じように扱われます。

さて、このシナリオで実際に変数の1つに別の名前を付けることができない理由はありますか?私はあなたの実際の変数がvarと呼ばれていないことを(期待して)いると思いますので、これが問題であるとは本当に思えません。同じ変数名を再利用したい場合は、それらを兄弟スコープに配置します。

if(true)
{
    string var = "VAR";
}

{
    string var = "New VAR!";
}

ただし、これはコンパイラーには有効ですが、コードの読み取り時にある程度の混乱を招く可能性があるため、ほとんどの場合はこれを避けることをお勧めします。

45
Noldorin

これは単なる間違いではありませんか?

いいえ、これはまったく問題ありません。これは、C#仕様の7.5.2.1項「単純な名前、ブロック内の不変の意味」の正しい実装です。

仕様は次のように述べています。


式または宣言子の単純名として指定された識別子が出現するたびに、その出現のローカル変数宣言スペース内で、式または宣言子の単純名と同じ識別子が出現するたびに同じものを参照する必要がありますエンティティ。このルールにより、特定のブロック、スイッチブロック、for-、foreach-、usingステートメント、または無名関数内で、名前の意味が常に同じになります。


C#が2つのスコープを区別できないのはなぜですか?

問題は無意味です。明らかにコンパイラーは2つのスコープを区別できます。コンパイラが2つのスコープを区別できなかった場合、どのようにしてエラーが発生する可能性がありますか?エラーメッセージは、2つの異なるスコープがあるため、スコープが区別されていることを示します

最初のIFスコープは、メソッドの残りの部分から完全に分離すべきではありませんか?

いいえ、できません。条件付きステートメントの結果としてブロックステートメントによって定義されたスコープ(およびローカル変数宣言スペース)は、字句的には、メソッドの本体を定義する外部ブロックの一部です。したがって、外側のブロックの内容に関する規則は、内側のブロックの内容に適用されます。

Ifの外部からvarを呼び出すことはできません。最初のvarが2番目のスコープに関連しないため、エラーメッセージは間違っています。

これは完全に間違っています。ローカル変数のスコープがなくなったという理由だけで、外側のブロックにエラーが含まれていないと結論するのは賢明です。エラーメッセージは正しいです。

ここでのエラーは、変数のスコープが他の変数のスコープと重複しているかどうかとは関係ありません。ここで関係があるのは、ブロック(外側のブロック)があり、同じ単純な名前を使用して2つのまったく異なるものを参照していることだけです。 C#では、単純な名前が最初にそれを使用するブロック全体で1つの意味を持つ必要があります

例えば:

class C 
{
    int x;
    void M()
    { 
        int x = 123;
    }
}

それは完全に合法です。外側のxのスコープが内側のxのスコープと重複していますが、これはエラーではありません。エラーとは:

class C 
{
    int x;
    void M()
    { 
        Console.WriteLine(x);
        if (whatever)
        {
            int x = 123;
        }
    }
}

なぜなら、単純な名前「x」は、Mの本体内の2つの異なることを意味します。「this.x」とローカル変数「x」を意味します。同じ単純な名前が同じブロック内の2つの完全に異なるものを意味する場合、これは違法です。

並列ブロックには、2つの異なる方法で使用される同じ単純な名前を含めることができます。これは合法です:

class C 
{
    int x;
    void M()
    { 
        if (whatever)
        {
            Console.WriteLine(x);
        }
        if (somethingelse)
        {
            int x = 123;
        }
    }
}

これは、xの2つの一貫性のない使用法を含む唯一のブロックが外部ブロックであり、そのブロックは直接ではないの使用法を含まないためです。 x "、間接的にのみ

34
Eric Lippert

これはC++で有効ですが、多くのバグと眠れない夜の原因となります。 C#の人たちは、コーダーが実際に望んでいるものではなく、ほとんどの場合バグであるため、警告/エラーをスローする方がよいと判断したと思います。

ここ は、このエラーが仕様のどの部分から発生しているかについての興味深い議論です。

編集(いくつかの例)-----

C++では、以下が有効です(そして、外側の宣言が内側のスコープの前後にあるかどうかは問題ではありません。前にあると、より興味深いバグが発生しやすくなります)。

void foo(int a)
{
    int count = 0;
    for(int i = 0; i < a; ++i)
    {
        int count *= i;
    }
    return count;
}

ここで、関数が数行長くなっていることを想像してください。エラーを見つけられないのは簡単です。コンパイラーは文句を言わず(昔ではなく、C++の新しいバージョンについては不明)、関数は常に0を返します。

Behaivourは明らかにバグであるため、c ++-lintプログラムまたはコンパイラがこれを指摘するとよいでしょう。バグでない場合は、内部変数の名前を変更するだけで簡単に回避できます。

傷害に侮辱を加えるために、GCCとVS6がforループのカウンター変数がどこに属するかについて異なる意見を持っていることを覚えています。 1つはそれが外側のスコープに属していたと言い、もう1つはそうではなかったと言いました。クロスプラットフォームのコードで作業するのは少し面倒です。行数を増やし続けるためのもう1つの例を挙げましょう。

for(int i = 0; i < 1000; ++i)
{
    if(array[i] > 100)
        break;
}

printf("The first very large value in the array exists at %d\n", i);

このコードはGCCではなくVS6 IIRCで機能しました。とにかく、C#はいくつかのことを整理しました。これは良いことです。

11