Ini Sparta, atau apakah itu?

121

Berikut ini adalah pertanyaan wawancara. Saya datang dengan solusi, tetapi saya tidak yakin mengapa itu berhasil.


Pertanyaan:

Tanpa mengubah Spartakelas, tulis beberapa kode yang menghasilkan MakeItReturnFalsereturn false.

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

Solusi saya: (SPOILER)

public class Place
{
public interface Sparta { }
}

Tapi kenapa Spartadi MakeItReturnFalse()rujuk {namespace}.Place.Spartabukan {namespace}.Sparta?

budi
sumber
5
Bisakah Anda menambahkan Peringatan Spoiler atau sesuatu ke Solution? Saya sangat kecewa sehingga saya tidak mendapatkan kesempatan untuk menyelesaikannya sendiri. Pertanyaannya memang luar biasa.
Karolis Kajenas
1
Saya awalnya menyertakan tag spoiler, namun itu diedit oleh komunitas beberapa kali sepanjang masa posting ini. Maaf soal itu.
budi
1
Saya tidak menyukai judul postingan ini; itu lebih cocok untuk codegolf.SE. Bisakah kita mengubahnya menjadi sesuatu yang benar-benar menggambarkan pertanyaan itu?
Supuhstar
3
Teka-teki lucu. Pertanyaan wawancara yang mengerikan, tapi teka-teki yang lucu. Sekarang setelah Anda tahu bagaimana dan mengapa ini bekerja, Anda harus mencoba versi yang lebih sulit ini: stackoverflow.com/q/41319962/88656
Eric Lippert

Jawaban:

117

Tapi kenapa Spartadi MakeItReturnFalse()rujuk {namespace}.Place.Spartabukan {namespace}.Sparta?

Pada dasarnya, karena itulah yang dikatakan oleh aturan pencarian nama. Dalam spesifikasi C # 5, aturan penamaan yang relevan ada di bagian 3.8 ("Namespace dan nama jenis").

Beberapa peluru pertama - terpotong dan diberi catatan - baca:

  • Jika namespace-or-type-name adalah dari bentuk Iatau bentuk I<A1, ..., AK> [jadi K = 0 dalam kasus kami] :
    • Jika K adalah nol dan namespace-or-type-name muncul dalam deklarasi metode umum [tidak, tidak ada metode generik]
    • Sebaliknya, jika namespace-or-type-name muncul dalam deklarasi tipe, maka untuk setiap instans tipe T (§10.3.1), dimulai dengan tipe instans dari deklarasi tipe tersebut dan dilanjutkan dengan tipe instans dari setiap kelas yang melingkupi atau deklarasi struct (jika ada):
      • Jika Knol dan deklarasi Tmenyertakan parameter tipe dengan nama I, maka namespace-or-type-name merujuk ke parameter tipe itu. [Nggak]
      • Jika tidak, jika namespace-or-type-name muncul di dalam isi deklarasi type, dan T atau salah satu tipe dasarnya berisi tipe yang dapat diakses bersarang yang memiliki parameter nama Idan Ktipe, maka namespace-or-type-name merujuk padanya tipe yang dibangun dengan argumen tipe yang diberikan. [Bingo!]
  • Jika langkah sebelumnya tidak berhasil, untuk setiap namespace N, dimulai dengan namespace di mana namespace-or-type-name terjadi, dilanjutkan dengan setiap namespace penutup (jika ada), dan diakhiri dengan namespace global, langkah-langkah berikut dievaluasi sampai entitas berada:
    • Jika Knol dan Imerupakan nama namespace di N, maka ... [Ya, itu akan berhasil]

Jadi poin terakhir itulah yang mengambil Sparta kelas jika poin pertama tidak menemukan apa pun ... tetapi ketika kelas dasar Placemendefinisikan antarmuka Sparta, itu ditemukan sebelum kita mempertimbangkanSparta kelas.

Perhatikan bahwa jika Anda menjadikan tipe bertingkat Place.Spartasebagai kelas daripada antarmuka, ia masih mengkompilasi dan mengembalikan false- tetapi kompilator mengeluarkan peringatan karena tahu bahwa instance dari Spartatidak akan pernah menjadi instance kelas Place.Sparta. Demikian juga jika Anda menyimpan Place.Spartaantarmuka tetapi membuat Spartakelas sealed, Anda akan mendapatkan peringatan karena tidak ada Spartainstance yang dapat mengimplementasikan antarmuka tersebut.

Jon Skeet
sumber
2
Pengamatan acak lainnya: Menggunakan Spartakelas asli , this is Placekembali true. Namun, menambahkan public interface Place { }ke Spartakelas menyebabkan this is Placeuntuk kembali false. Membuat kepalaku berputar.
budi
@ Budi: Benar, karena sekali lagi, peluru sebelumnya ditemukan Placesebagai antarmuka.
Jon Skeet
22

Saat menentukan nama sesuai nilainya, "kedekatan" dari definisi tersebut digunakan untuk menyelesaikan ambiguitas. Definisi apapun yang "paling dekat" adalah definisi yang dipilih.

Antarmuka Spartadidefinisikan dalam kelas dasar. Kelas Spartadidefinisikan dalam namespace yang memuat. Hal-hal yang didefinisikan dalam kelas dasar "lebih dekat" daripada hal-hal yang didefinisikan dalam namespace yang sama.

Pelayanan
sumber
1
Dan bayangkan jika pencarian nama tidak berfungsi seperti ini. Kemudian, kode kerja yang berisi kelas dalam akan rusak jika ada yang menambahkan kelas tingkat atas dengan nama yang sama.
dan04
1
@ dan04: Namun sebaliknya, kode kerja yang tidak berisi kelas bersarang akan rusak jika ada yang menambahkan kelas bertingkat dengan nama yang sama dengan kelas tingkat atas. Jadi, ini bukan skenario "menang" total.
Jon Skeet
1
@JonSkeet Saya akan mengatakan menambahkan kelas bersarang seperti itu adalah perubahan di area di mana kode kerja memiliki alasan yang masuk akal untuk terpengaruh, dan perhatikan perubahannya. Menambahkan kelas tingkat atas yang sama sekali tidak terkait jauh lebih jauh.
Angew tidak lagi bangga dengan SO
2
@ JonSkeet Bukankah itu hanya masalah Kelas Dasar yang Rapuh dengan penyamaran yang sedikit berbeda?
João Mendes
1
@ JoãoMendes: Ya, cukup banyak.
Jon Skeet
1

Pertanyaan yang indah! Saya ingin menambahkan penjelasan yang sedikit lebih panjang untuk mereka yang tidak melakukan C # setiap hari ... karena pertanyaan ini merupakan pengingat yang baik tentang masalah resolusi nama secara umum.

Ambil kode aslinya, sedikit dimodifikasi dengan cara berikut:

  • Mari kita cetak nama tipe daripada membandingkannya seperti pada ekspresi asli (yaitu return this is Sparta).
  • Mari tentukan antarmuka Athenadi filePlace superclass untuk mengilustrasikan resolusi nama antarmuka.
  • Mari kita juga mencetak nama tipe thiskarena terikat di Spartakelas, hanya untuk memperjelas semuanya.

Kodenya terlihat seperti ini:

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));
    }
}

Kami sekarang membuat Spartaobjek dan memanggil tiga metode.

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

Output yang kami dapatkan adalah:

Sparta
Athena
Place+Athena

Namun, jika kita memodifikasi kelas Place dan mendefinisikan antarmuka Sparta:

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

maka inilah Sparta- antarmuka - yang akan tersedia terlebih dahulu untuk mekanisme pencarian nama dan keluaran kode kita akan berubah menjadi:

Sparta
Place+Sparta
Place+Athena

Jadi kami secara efektif mengacaukan perbandingan tipe dalam MakeItReturnFalsedefinisi fungsi hanya dengan mendefinisikan antarmuka Sparta di superclass, yang ditemukan pertama kali oleh resolusi nama.

Tetapi mengapa C # memilih untuk memprioritaskan antarmuka yang ditentukan dalam superclass dalam resolusi nama? @JonSet tahu! Dan jika Anda membaca jawabannya, Anda akan mendapatkan detail dari protokol resolusi nama di C #.

mircealungu
sumber