親スコープの変数と同じ名前の子変数を宣言できるのはなぜですか?


23

同じ名前の変数がすでにある関数内で宣言されたアクションのパラメーターとして変数名を誤って再利用するコードを最近書きました。例えば:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

重複を見つけたとき、コードが完全にコンパイルされて実行されたことに驚きました。これは、C#のスコープについて知っていることに基づいて期待する動作ではありません。Laogda Scope Clarificationなど、いくつかの簡単なグーグル検索により、同様のコードエラーを生成することを訴えるSOの質問が表示されました。(念のため、サンプルコードをIDEに貼り付けて、実行されるかどうかを確認しました。完全に実行されます。)さらに、Visual Studioで[名前の変更]ダイアログに入ると、最初の名前が​​名前の競合として強調表示されます。x

このコードはなぜ機能するのですか?Visual Studio 2019でC#8を使用しています。


1
ラムダは、コンパイラーによって生成されるクラスのxメソッドに移動されるため、そのメソッドのパラメーター全体がスコープの外に移動されます。例については、sharplabを参照してください。
Lasse V. Karlsen、

6
これはC#7.3をターゲットにするとコンパイルされないため、C#8に限定されているように見えることに注意してください。
Jonathon Chase

リンクされた質問のコードは、sharplabでも正常にコンパイルされます。これは最近の変更である可能性があります。
Lasse V. Karlsen、

2
:(答えなし)デュープたstackoverflow.com/questions/58639477/...
bolovを

回答:


26

このコードはなぜ機能するのですか?Visual Studio 2019でC#8を使用しています。

あなたはあなた自身の質問に答えました!これは、C#8を使用しているためです。

C#1から7までのルールは次のとおりです。単純な名前を使用して、同じローカルスコープ内の2つの異なることを意味することはできません。(実際のルールはそれよりも少し複雑でしたが、面倒な方法を説明しています。詳細はC#仕様を参照してください。)

このルールの意図は、例で話しているような状況を回避することでした。この状況では、ローカルの意味について非常に混乱しやすくなります。特に、このルールは次のような混乱を防ぐために設計されました。

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

そして今、私たちの体の中でM、とxは両方this.xとローカルを意味する状況にありxます。

善意はあるものの、このルールにはいくつかの問題がありました。

  • 仕様どおりに実装されていません。単純な名前を、たとえばタイプとプロパティの両方として使用できる状況がありましたが、エラー検出ロジックに欠陥があったため、これらは常にエラーとしてフラグが付けられるとは限りませんでした。(下記参照)
  • エラーメッセージは紛らわしく表現され、一貫性のない方法で報告されました。この状況では、複数の異なるエラーメッセージがありました。彼らは一貫して犯人を特定した。それは時々 、ある内部使用は時々 、アウトと呼ばれることになるアウター、時にはそれだけで混乱されました。

私はこれを整理するためにRoslynの書き換えで努力しました。新しいエラーメッセージをいくつか追加し、エラーが報告された場所に関して古いメッセージに一貫性を持たせました。しかし、この努力は少なすぎ、遅すぎました。

C#チームはC#8について、ルール全体が妨げているよりも混乱を引き起こしていると判断し、ルールは言語から削除されました。(退職がいつ起こったかを判断してくれたJonathon Chaseに感謝します。)

この問題の履歴と、私がそれを修正しようとした方法を知りたい場合は、私が書いた次の記事を参照してください。

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

第3部の終わりに、この機能と「カラーカラー」機能、つまり次のことを可能にする機能との間に相互作用があることにも触れました。

class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

ここでは、単純名Colorを使用してthis.Color、列挙型とその両方を参照していますColor。仕様を厳密に読み取ると、これはエラーになるはずですが、この場合、仕様が間違っていて、それを許可することを意図していたため、このコードは明確であり、開発者に変更させるのは面倒です。

私はこれらの2つのルールの間のすべての奇妙な相互作用を説明する記事を書いたことはありませんでした。今、そうするのは少し無意味です。


問題のコードはC#6、7、7.1、7.2、および7.3のコンパイルに失敗し、「CS0136: 'x'という名前のローカルまたはパラメーターはこのスコープでは宣言できないため、その名前は...」となります。まだC#8まで施行ように、それはルールのように思える
ジョナサン・チェース・

@JonathonChase:ありがとう!
Eric Lippert
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.