Tại sao tôi có thể khai báo một biến con có cùng tên với một biến trong phạm vi cha?


23

Tôi đã viết một số mã gần đây khi tôi vô tình sử dụng lại một tên biến làm tham số của một hành động được khai báo trong một hàm đã có một biến có cùng tên. Ví dụ:

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

Khi tôi phát hiện sự trùng lặp, tôi rất ngạc nhiên khi thấy mã được biên dịch và chạy hoàn hảo, đó không phải là hành vi mà tôi mong đợi dựa trên những gì tôi biết về phạm vi trong C #. Một số nhanh Googling bật lên SO câu hỏi mà phàn nàn rằng mã tương tự như không tạo ra một lỗi, chẳng hạn như Lambda Phạm vi làm rõ . (Tôi đã dán mã mẫu đó vào IDE của mình để xem nó có chạy hay không, chỉ để đảm bảo; nó chạy hoàn hảo.) Ngoài ra, khi tôi nhập hộp thoại Đổi tên trong Visual Studio, lần đầu tiên xđược tô sáng là xung đột tên.

Tại sao mã này hoạt động? Tôi đang sử dụng C # 8 với Visual Studio 2019.


1
Lambda được chuyển đến một phương thức trên một lớp được trình biên dịch tạo ra và do đó toàn bộ xtham số của phương thức đó được chuyển ra khỏi phạm vi. Xem sharplab cho một ví dụ.
Lasse V. Karlsen

6
Điều đáng chú ý ở đây là điều này sẽ không được biên dịch khi nhắm mục tiêu C # 7.3, vì vậy điều này dường như là độc quyền cho C # 8.
Jonathon Chase

Mã trong câu hỏi được liên kết cũng biên dịch tốt trong sharplab . Đây có thể là một sự thay đổi gần đây.
Lasse V. Karlsen

2
tìm thấy một bản dupe (không có câu trả lời): stackoverflow.com/questions/58639477/ trên
bolov

Câu trả lời:


26

Tại sao mã này hoạt động? Tôi đang sử dụng C # 8 với Visual Studio 2019.

Bạn đã trả lời câu hỏi của riêng bạn! Đó là bởi vì bạn đang sử dụng C # 8.

Quy tắc từ C # 1 đến 7 là: một tên đơn giản không thể được sử dụng để có nghĩa là hai điều khác nhau trong cùng một phạm vi địa phương. (Quy tắc thực tế phức tạp hơn một chút so với quy tắc đó nhưng mô tả mức độ tẻ nhạt; xem thông số kỹ thuật C # để biết chi tiết.)

Mục đích của quy tắc này là để ngăn chặn loại tình huống mà bạn đang nói đến trong ví dụ của bạn, nơi nó trở nên rất dễ bị nhầm lẫn về ý nghĩa của địa phương. Đặc biệt, quy tắc này được thiết kế để ngăn ngừa những nhầm lẫn như:

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

Và bây giờ chúng ta có một tình huống bên trong cơ thể M, xcó nghĩa là cả this.xvà địa phương x.

Mặc dù có thiện chí, có một số vấn đề với quy tắc này:

  • Nó đã không được thực hiện để spec. Có những tình huống mà một tên đơn giản có thể được sử dụng như, cả loại và thuộc tính, nhưng chúng không phải luôn được gắn cờ là lỗi vì logic phát hiện lỗi bị sai sót. (Xem bên dưới)
  • Các thông báo lỗi được diễn đạt một cách khó hiểu và được báo cáo không nhất quán. Có nhiều thông báo lỗi khác nhau cho tình huống này. Họ không nhất quán xác định người phạm tội; nghĩa là, đôi khi việc sử dụng bên trong sẽ được gọi ra, đôi khi là bên ngoài và đôi khi nó chỉ gây nhầm lẫn.

Tôi đã nỗ lực viết lại Roslyn để sắp xếp thứ này; Tôi đã thêm một số thông báo lỗi mới và làm cho các thông báo lỗi cũ nhất quán về nơi báo cáo lỗi. Tuy nhiên, nỗ lực này là quá ít, quá muộn.

Nhóm C # đã quyết định cho C # 8 rằng toàn bộ quy tắc đã gây ra nhiều nhầm lẫn hơn là ngăn chặn và quy tắc này đã bị loại bỏ khỏi ngôn ngữ. (Cảm ơn Jonathon Chase vì đã xác định thời điểm nghỉ hưu xảy ra.)

Nếu bạn muốn tìm hiểu lịch sử của vấn đề này và cách tôi cố gắng khắc phục nó, hãy xem những bài viết tôi đã viết về nó:

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/ bối rối-giới thiệu-for-a-confuses-tính năng-part-one /

https://ericlippert.com/2014/09/29/confuses-errors-for-a- bối rối-tính năng-part-two /

https://ericlippert.com/2014/10/03/ bối rối-giới thiệu-for-a- bối rối-tính năng-part-three /

Cuối phần ba tôi đã lưu ý rằng còn có sự tương tác giữa tính năng này và tính năng "Màu màu" - đó là tính năng cho phép:

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

Ở đây chúng tôi đã sử dụng tên đơn giản Colorđể chỉ cả hai this.Colorvà loại liệt kê Color; theo một cách đọc nghiêm ngặt về đặc tả kỹ thuật này sẽ là một lỗi, nhưng trong trường hợp này, thông số kỹ thuật đã sai và ý định là cho phép nó, vì mã này không rõ ràng và sẽ gây phiền toái cho nhà phát triển thay đổi nó.

Tôi chưa bao giờ viết bài báo đó mô tả tất cả các tương tác kỳ lạ giữa hai quy tắc này và bây giờ sẽ hơi vô nghĩa khi làm như vậy!


Mã trong câu hỏi không thể biên dịch cho C # 6, 7, 7.1, 7.2 và 7.3, đưa ra "CS0136: Không thể khai báo một tham số hoặc tham số có tên 'x' trong phạm vi này vì tên đó ...". Có vẻ như quy tắc vẫn được thi hành cho đến C # 8.
Jonathon Chase

@JonathonChase: Cảm ơn!
Eric Lippert
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.