12

次のコードを見てみましょう。

class Foo
{
   string bar;

   public void Method()
   {
      if (!String.IsNullOrEmpty(this.bar))
      {
         string bar = "Hello";
         Console.Write(bar);
      }
   }
}

これはコンパイルされ、すべて問題ありません。ただし、this.プレフィックスを削除しましょう。

class Foo
{
   string bar;

   public void Method()
   {
      if (!String.IsNullOrEmpty(bar)) // <-- Removed "this."
      {
         string bar = "Hello";
         Console.Write(bar);
      }
   }
}

この場合、コンパイル エラーが発生します。これがエラーであることには同意しますが、私を混乱させるのはエラーの場所です。エラーは次の行で発生します。

string bar = "Hello";

メッセージ付き:

「bar」という名前のローカル変数をこのスコープで宣言することはできません。これは、「親または現在の」スコープで他の何かを示すために既に使用されている「bar」に別の意味を与えるためです。

コンパイラについて私が理解していることから、宣言はメソッドの先頭にbar持ち上げられます。Method()ただし、その場合は、次の行:

if (!String.IsNullOrEmpty(bar))

barインスタンスフィールドまたはまだ宣言されていないローカル変数への参照である可能性があるため、あいまいであると見なす必要があります。

私には、削除this.すると別の行でコンパイルエラーが発生する可能性があるのは奇妙に思えます。言い換えれば、ローカルbar変数の宣言は完全に有効ですが、スコープ内で以前に曖昧な参照が行われていない限りは有効です(コメントアウトするとエラーは消えます)。barif (!String.IsNullOrEmpty(bar))

それはすべてかなり衒学的に思えるので、あなたの質問は何ですか?:

私の質問は、コンパイラがスコープで宣言される前に変数へのあいまいな参照を許可するのに、宣言自体に冗長としてフラグを立てる理由です。barinへのあいまいな参照String.IsNullOrEmpty()は、エラーのより正確な場所であるべきではありませんか? 私の例では、もちろん簡単に見つけることができますが、実際にこの問題に出くわしたとき、参照はページアップであり、追跡するのははるかに困難でした.

4

2 に答える 2

11

コンパイラについて私が理解していることから、 bar の宣言は Method() メソッドの先頭に持ち上げられます。

いいえ、そうではありません。

エラーメッセージはここで非常に正確です:

「bar」という名前のローカル変数をこのスコープで宣言することはできません。これは、「親または現在の」スコープで他の何かを示すために既に使用されている「bar」に別の意味を与えるためです。

違反している C# 仕様の部分は、セクション 7.6.2.1 (C# 4 および 5 仕様) です。

ブロック内での不変の意味
式または宣言子の完全な単純名 (型引数リストなし) として指定された識別子が出現するたびに、その出現をすぐに囲むローカル変数宣言スペース (§3.3) 内で、式または宣言子の完全な単純名と同じ識別子は、同じエンティティを参照する必要があります。この規則により、特定のブロック、switch ブロック、for、foreach、using ステートメント、または無名関数内で、名前の意味が常に同じになることが保証されます。

注釈付きの C# 仕様では、Eric Lippert からの役立つ注釈があります。

このルールのより微妙な望ましい結果の 1 つは、ローカル変数宣言の移動を伴うリファクタリングを行うことがより安全になることです。単純な名前のセマンティクスを変更するようなリファクタリングは、コンパイラによってキャッチされます。

ただし、他のことは別として、これは明確にするのにちょうど良いようです。2 番目のバージョンが許可されたとしても、最初のバージョンの方が明確な IMO です。コンパイラは、病理学的に不明確なコードを書かないようにします。コードを簡単に修正して、どこで何を意味するのかを明確にすることができます。

別の言い方をすれば、本当に2 番目のバージョンを作成できるようになりたいですか?

特に:

私の例では、もちろん簡単に見つけることができますが、実際にこの問題に出くわしたとき、参照はページアップであり、追跡するのははるかに困難でした.

...そして、それを許可する方が合理的ですか? まったく逆だと思います。また、単一のメソッドが「ページ」長にならないように、コードをリファクタリングすることを強く推奨するものとして扱う必要があります。

于 2012-11-27T18:25:21.040 に答える
1

bar の 2 番目の定義はメソッド レベルにプルされず、そのスコープは if ブロックです。たとえば、これは完全に有効です。

class Foo
{
    private string bar;

    public void Method()
    {
        if (!String.IsNullOrEmpty(bar)) // <-- No more this.
        {
            string bar1 = "Hello";
            Console.Write(bar);
        }

        if (!String.IsNullOrEmpty(bar)) // <-- No more this.
        {
            string bar1 = "Hello";
            Console.Write(bar);
        }
    }
}

その理由は、もはやあいまいではなく、 /barの外側と内側のスコープの間に明確な名前の違いがあるためです。コンパイラは、ローカル定義による外側のスコープのオーバーライドを許可しません。barbar1bar

于 2012-11-27T18:26:17.893 に答える