Đây là Sparta, hay là nó?


121

Sau đây là câu hỏi phỏng vấn. Tôi đã đưa ra một giải pháp, nhưng tôi không chắc tại sao nó hoạt động.


Câu hỏi:

Không sửa đổi Spartalớp, hãy viết một số mã MakeItReturnFalsetrả về false.

public class Sparta : Place
{
    public bool MakeItReturnFalse()
    {
        return this is Sparta;
    }
}

Giải pháp của tôi: (SPOILER)

public class Place
{
public interface Sparta { }
}

Nhưng tại sao Spartatrong MakeItReturnFalse()tham chiếu đến {namespace}.Place.Spartathay vì {namespace}.Sparta?


5
Bạn vui lòng thêm Spoiler Alert hoặc cái gì đó vào Solution được không? Tôi rất thất vọng, tôi không có cơ hội để giải quyết nó một mình. Câu hỏi thực sự là tuyệt vời.
Karolis Kajenas

1
Ban đầu tôi bao gồm các thẻ spoiler, tuy nhiên nó đã được cộng đồng chỉnh sửa nhiều lần trong suốt thời gian của bài đăng này. Xin lỗi vì điều đó.
budi

1
Tôi không thích tiêu đề của bài đăng này; nó phù hợp hơn cho codegolf.SE. Chúng ta có thể thay đổi nó thành một cái gì đó thực sự mô tả câu hỏi không?
Supuhstar

3
Câu đố dễ thương. Câu hỏi phỏng vấn khủng khiếp, nhưng câu đố dễ thương. Bây giờ bạn đã biết cách thức và lý do tại sao tính năng này hoạt động, bạn nên thử ở phiên bản khó hơn này: stackoverflow.com/q/41319962/88656
Eric Lippert

Câu trả lời:


117

Nhưng tại sao Spartatrong MakeItReturnFalse()tham chiếu đến {namespace}.Place.Spartathay vì {namespace}.Sparta?

Về cơ bản, vì đó là những gì quy tắc tra cứu tên nói. Trong đặc tả C # 5, các quy tắc đặt tên có liên quan nằm trong phần 3.8 ("Không gian tên và tên kiểu").

Vài gạch đầu dòng đầu tiên - được cắt ngắn và chú thích - đọc:

  • Nếu không gian tên-hoặc-kiểu-tên có dạng Ihoặc có dạng I<A1, ..., AK> [vì vậy K = 0 trong trường hợp của chúng ta] :
    • Nếu K bằng 0 và không gian tên-hoặc-kiểu-tên xuất hiện trong khai báo phương thức chung [nope, không có phương thức chung]
    • Ngược lại, nếu không gian tên-hoặc-kiểu-tên xuất hiện trong khai báo kiểu, thì đối với mỗi kiểu thể hiện T (§10.3.1), bắt đầu với kiểu thể hiện của khai báo kiểu đó và tiếp tục với kiểu thể hiện của mỗi lớp bao quanh hoặc khai báo struct (nếu có):
      • Nếu Kbằng 0 và khai báo Tbao gồm một tham số kiểu với tên I, thì không gian tên-hoặc-kiểu-tên tham chiếu đến tham số kiểu đó. [Không]
      • Ngược lại, nếu không gian tên-hoặc-kiểu-tên xuất hiện trong phần nội dung của khai báo kiểu và T hoặc bất kỳ kiểu cơ sở nào của nó chứa kiểu có thể truy cập lồng nhau có các tham số tên IKkiểu, thì không gian tên-hoặc-kiểu-tên đề cập đến điều đó kiểu được xây dựng với các đối số kiểu đã cho. [Chơi lô tô!]
  • Nếu các bước trước đó không thành công thì đối với mỗi không gian tên N, bắt đầu với không gian tên mà không gian tên-hoặc-loại-tên xuất hiện, tiếp tục với từng không gian tên bao quanh (nếu có) và kết thúc bằng không gian tên chung, các bước sau được đánh giá cho đến khi một thực thể được định vị:
    • Nếu Klà 0 và Ilà tên của không gian tên trong Nthì ... [Có, điều đó sẽ thành công]

Vì vậy, gạch đầu dòng cuối cùng là thứ chọn Sparta lớp nếu dấu đầu dòng đầu tiên không tìm thấy gì ... nhưng khi lớp cơ sở Placexác định giao diện Sparta, nó sẽ được tìm thấy trước khi chúng ta xem xét Spartalớp.

Lưu ý rằng nếu bạn đặt kiểu lồng nhau Place.Spartalà một lớp chứ không phải là một giao diện, nó vẫn biên dịch và trả về false- nhưng trình biên dịch đưa ra cảnh báo vì nó biết rằng một thể hiện của Spartasẽ không bao giờ là một thể hiện của lớp Place.Sparta. Tương tự như vậy, nếu bạn giữ Place.Spartamột giao diện nhưng tạo ra Spartalớp sealed, bạn sẽ nhận được cảnh báo vì không có Spartaphiên bản nào có thể triển khai giao diện.


2
Một quan sát ngẫu nhiên khác: Sử dụng Spartalớp ban đầu , this is Placetrả về true. Tuy nhiên, việc thêm public interface Place { }vào các Spartanguyên nhân lớp sẽ this is Placetrả về false. Khiến đầu tôi quay cuồng.
budi

@budi: Đúng, vì một lần nữa, dấu đầu dòng trước đó được tìm thấy Placelàm giao diện.
Jon Skeet

22

Khi phân giải một tên thành giá trị của nó, "độ gần gũi" của định nghĩa được sử dụng để giải quyết những điều không rõ ràng. Bất cứ định nghĩa nào là "gần nhất" là định nghĩa được chọn.

Giao diện Spartađược định nghĩa trong một lớp cơ sở. Lớp Spartađược định nghĩa trong không gian tên chứa. Những thứ được định nghĩa trong một lớp cơ sở "gần gũi" hơn những thứ được định nghĩa trong cùng một không gian tên.


1
Và hãy tưởng tượng nếu tra cứu tên không hoạt động theo cách này. Sau đó, mã làm việc có chứa một lớp bên trong sẽ bị hỏng nếu có ai đó tình cờ thêm một lớp cấp cao nhất có cùng tên.
dan04

1
@ dan04: Nhưng thay vào đó, mã làm việc không chứa lớp lồng nhau sẽ bị hỏng nếu có ai đó tình cờ thêm lớp lồng nhau có cùng tên với lớp cấp cao nhất. Vì vậy, nó không hẳn là một kịch bản "toàn thắng".
Jon Skeet

1
@JonSkeet Tôi muốn nói rằng việc thêm một lớp lồng nhau như vậy là một sự thay đổi trong một khu vực mà mã làm việc có lý do hợp lý để bị ảnh hưởng và theo dõi các thay đổi. Việc thêm một lớp cấp cao nhất hoàn toàn không liên quan sẽ bị loại bỏ nhiều hơn.
Angew không còn tự hào về SO

2
@JonSkeet Tuy nhiên, đó không chỉ là vấn đề của Lớp cơ sở giòn ở một chiêu bài hơi khác?
João Mendes

1
@ JoãoMendes: Vâng, khá nhiều.
Jon Skeet

1

Câu hỏi hay! Tôi muốn thêm một lời giải thích dài hơn một chút cho những người không làm C # hàng ngày ... bởi vì câu hỏi là một lời nhắc nhở tốt về các vấn đề phân giải tên nói chung.

Lấy mã gốc, được sửa đổi một chút theo những cách sau:

  • Hãy in ra các tên kiểu thay vì so sánh chúng như trong biểu thức ban đầu (tức là return this is Sparta).
  • Hãy xác định giao diện Athenatrong Placelớp cha để minh họa độ phân giải tên giao diện.
  • Cũng hãy in ra tên kiểu của thisnó được ràng buộc trong Spartalớp, chỉ để làm cho mọi thứ thật rõ ràng.

Mã trông như thế này:

public class Place {
    public interface Athena { }
}

public class Sparta : Place
{
    public void printTypeOfThis()
    {
        Console.WriteLine (this.GetType().Name);
    }

    public void printTypeOfSparta()
    {
        Console.WriteLine (typeof(Sparta));
    }

    public void printTypeOfAthena()
    {
        Console.WriteLine (typeof(Athena));
    }
}

Bây giờ chúng ta tạo một Spartađối tượng và gọi ba phương thức.

public static void Main(string[] args)
    {
        Sparta s = new Sparta();
        s.printTypeOfThis();
        s.printTypeOfSparta();
        s.printTypeOfAthena();
    }
}

Đầu ra chúng tôi nhận được là:

Sparta
Athena
Place+Athena

Tuy nhiên, nếu chúng ta sửa đổi lớp Place và xác định giao diện Sparta:

   public class Place {
        public interface Athena { }
        public interface Sparta { } 
    }

thì nó Sparta- giao diện - sẽ có sẵn đầu tiên cho cơ chế tra cứu tên và đầu ra mã của chúng tôi sẽ thay đổi thành:

Sparta
Place+Sparta
Place+Athena

Vì vậy, chúng ta đã nhầm lẫn hiệu quả với việc so sánh kiểu trong MakeItReturnFalseđịnh nghĩa hàm chỉ bằng cách xác định giao diện Sparta trong lớp cha, được tìm thấy đầu tiên bằng độ phân giải tên.

Nhưng tại sao C # lại chọn ưu tiên các giao diện được xác định trong lớp cha trong độ phân giải tên? @JonSkeet biết! Và nếu bạn đọc câu trả lời của anh ấy, bạn sẽ nhận được chi tiết của giao thức phân giải tên trong C #.

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.