Mengapa saya bisa mendeklarasikan variabel turunan dengan nama yang sama dengan variabel dalam lingkup induknya?

23

Saya menulis beberapa kode baru-baru ini di mana saya tidak sengaja menggunakan kembali nama variabel sebagai parameter tindakan yang dinyatakan dalam fungsi yang sudah memiliki variabel dengan nama yang sama. Sebagai contoh:

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

Ketika saya melihat duplikasi, saya terkejut melihat bahwa kode dikompilasi dan berjalan dengan sempurna, yang bukan perilaku yang saya harapkan berdasarkan pada apa yang saya ketahui tentang ruang lingkup di C #. Beberapa Googling cepat muncul pertanyaan SO yang mengeluh bahwa kode yang sama tidak menghasilkan kesalahan, seperti Klarifikasi Lingkup Lambda . (Saya menyisipkan kode sampel itu ke IDE saya untuk melihat apakah itu akan berjalan, hanya untuk memastikan; itu berjalan dengan sempurna.) Selain itu, ketika saya memasukkan dialog Ganti nama di Visual Studio, yang pertama xdisorot sebagai konflik nama.

Mengapa kode ini berfungsi? Saya menggunakan C # 8 dengan Visual Studio 2019.

stellr42
sumber
1
Lambda dipindahkan ke metode pada kelas yang dihasilkan oleh kompiler, dan dengan demikian seluruh xparameter untuk metode itu akan dipindahkan dari ruang lingkup. Lihat sharplab untuk contohnya.
Lasse V. Karlsen
6
Mungkin perlu dicatat di sini bahwa ini tidak akan dikompilasi ketika menargetkan C # 7.3, jadi ini tampaknya eksklusif untuk C # 8.
Jonathon Chase
Kode dalam pertanyaan yang ditautkan juga mengkompilasi dengan baik di sharplab . Ini mungkin perubahan terbaru.
Lasse V. Karlsen
2
menemukan korban penipuan (tanpa jawaban): stackoverflow.com/questions/58639477/…
bolov

Jawaban:

26

Mengapa kode ini berfungsi? Saya menggunakan C # 8 dengan Visual Studio 2019.

Anda telah menjawab pertanyaan Anda sendiri! Itu karena Anda menggunakan C # 8.

Aturan dari C # 1 hingga 7 adalah: nama sederhana tidak dapat digunakan untuk mengartikan dua hal yang berbeda dalam lingkup lokal yang sama. (Aturan sebenarnya sedikit lebih kompleks dari itu tetapi menggambarkan bagaimana itu membosankan; lihat spesifikasi C # untuk detailnya.)

Maksud aturan ini adalah untuk mencegah situasi yang sedang Anda bicarakan dalam contoh Anda, di mana menjadi sangat mudah untuk dibingungkan tentang arti lokal. Secara khusus, aturan ini dirancang untuk mencegah kebingungan seperti:

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

Dan sekarang kita memiliki situasi di mana di dalam tubuh M, xberarti keduanya this.xdan lokal x.

Meskipun bermaksud baik, ada sejumlah masalah dengan aturan ini:

  • Itu tidak diterapkan untuk spec. Ada situasi di mana nama sederhana dapat digunakan sebagai, katakanlah, baik tipe dan properti, tetapi ini tidak selalu ditandai sebagai kesalahan karena logika deteksi kesalahan cacat. (Lihat di bawah)
  • Pesan-pesan kesalahan itu membingungkan, dan dilaporkan secara tidak konsisten. Ada beberapa pesan kesalahan yang berbeda untuk situasi ini. Mereka secara tidak konsisten mengidentifikasi pelaku; yaitu, kadang-kadang penggunaan batin akan dipanggil, kadang-kadang luar , dan kadang-kadang hanya membingungkan.

Saya berusaha keras dalam menulis ulang Roslyn untuk menyelesaikan masalah ini; Saya menambahkan beberapa pesan kesalahan baru, dan membuat yang lama konsisten mengenai di mana kesalahan itu dilaporkan. Namun, upaya ini terlalu sedikit, sudah terlambat.

Tim C # memutuskan untuk C # 8 bahwa seluruh aturan menyebabkan lebih banyak kebingungan daripada yang mencegahnya, dan aturan tersebut dipensiunkan dari bahasa. (Terima kasih Jonathon Chase untuk menentukan kapan pensiun terjadi.)

Jika Anda tertarik untuk mempelajari sejarah masalah ini dan bagaimana saya berusaha memperbaikinya, lihat artikel yang saya tulis tentang ini:

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/

Pada akhir bagian tiga saya mencatat bahwa ada juga interaksi antara fitur ini dan fitur "Warna Warna" - yaitu, fitur yang memungkinkan:

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

Di sini kita telah menggunakan nama sederhana Coloruntuk merujuk pada keduanya this.Colordan jenis yang disebutkan Color; menurut pembacaan yang ketat dari spesifikasi ini harus menjadi kesalahan, tetapi dalam kasus ini spesifikasi itu salah dan niat untuk memperbolehkannya, karena kode ini tidak ambigu dan akan membingungkan untuk membuat pengembang mengubahnya.

Saya tidak pernah menulis artikel yang menggambarkan semua interaksi aneh antara kedua aturan ini, dan itu akan menjadi sedikit sia-sia untuk melakukannya sekarang!

Eric Lippert
sumber
Kode dalam pertanyaan gagal dikompilasi untuk C # 6, 7, 7.1, 7.2, dan 7.3, memberikan "CS0136: Parameter lokal atau bernama 'x' tidak dapat dideklarasikan dalam lingkup ini karena nama itu ...". Sepertinya aturan itu masih diberlakukan sampai C # 8.
Jonathon Chase
@ JonathonChase: Terima kasih!
Eric Lippert