นี่คือสปาร์ตาหรือมัน?


121

ต่อไปนี้เป็นคำถามสัมภาษณ์ ฉันคิดวิธีแก้ปัญหาขึ้นมา แต่ไม่แน่ใจว่าทำไมถึงได้ผล


คำถาม:

โดยไม่มีการแก้ไขSpartaระดับเขียนโค้ดบางอย่างที่ทำให้ผลตอบแทนMakeItReturnFalsefalse

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

วิธีแก้ปัญหาของฉัน: (สปอยเลอร์)

public class Place
{
public interface Sparta { }
}

แต่ทำไมSpartaในMakeItReturnFalse()หมายถึง{namespace}.Place.Spartaแทน{namespace}.Sparta?


5
คุณช่วยเพิ่ม Spoiler Alert หรืออะไรบางอย่างใน Solution ได้ไหม? ฉันรู้สึกผิดหวังมากฉันไม่ได้รับโอกาสที่จะแก้ปัญหาด้วยตัวเอง คำถามนั้นน่าทึ่งมาก
Karolis Kajenas

1
ในตอนแรกฉันใส่แท็กสปอยเลอร์ แต่ชุมชนได้แก้ไขหลายครั้งตลอดอายุของโพสต์นี้ ขอโทษด้วยกับเรื่องนั้น.
budi

1
ฉันไม่ชอบชื่อของโพสต์นี้ เหมาะสำหรับ codegolf.SE เราสามารถเปลี่ยนเป็นสิ่งที่อธิบายคำถามได้หรือไม่?
Supuhstar

3
ปริศนาน่ารัก คำถามสัมภาษณ์แย่มาก แต่เป็นปริศนาที่น่ารัก ตอนนี้คุณรู้แล้วว่ามันได้ผลอย่างไรและทำไมคุณควรลองใช้เวอร์ชันที่ยากกว่านี้: stackoverflow.com/q/41319962/88656
Eric Lippert

คำตอบ:


117

แต่ทำไมSpartaในMakeItReturnFalse()หมายถึง{namespace}.Place.Spartaแทน{namespace}.Sparta?

โดยทั่วไปแล้วเพราะนั่นคือสิ่งที่กฎการค้นหาชื่อพูด ในข้อกำหนด C # 5 กฎการตั้งชื่อที่เกี่ยวข้องอยู่ในส่วน 3.8 ("เนมสเปซและชื่อประเภท")

สัญลักษณ์แสดงหัวข้อย่อยคู่แรก - ถูกตัดทอนและมีคำอธิบายประกอบ - อ่าน:

  • หากเนมสเปซหรือชื่อประเภทอยู่ในรูปแบบIหรือรูปแบบI<A1, ..., AK> [ดังนั้น K = 0 ในกรณีของเรา] :
    • ถ้า K เป็นศูนย์และชื่อเนมสเปซหรือชนิดปรากฏขึ้นภายในการประกาศเมธอดทั่วไป[ไม่ไม่ใช่วิธีการทั่วไป]
    • มิฉะนั้นหากชื่อเนมสเปซหรือประเภทปรากฏขึ้นภายในการประกาศประเภทดังนั้นสำหรับแต่ละประเภทอินสแตนซ์ T (§10.3.1) เริ่มต้นด้วยประเภทอินสแตนซ์ของการประกาศประเภทนั้นและดำเนินการต่อด้วยประเภทอินสแตนซ์ของแต่ละคลาสที่ปิดล้อมหรือ การประกาศโครงสร้าง (ถ้ามี):
      • ถ้าKเป็นศูนย์และการประกาศTรวมพารามิเตอร์ type ที่มีชื่อIดังนั้น namespace-or-type-name จะอ้างถึงพารามิเตอร์ type นั้น [Nope]
      • มิฉะนั้นหากชื่อเนมสเปซหรือชนิดปรากฏขึ้นภายในเนื้อความของการประกาศประเภทและT หรือประเภทพื้นฐานใด ๆ มีประเภทที่สามารถเข้าถึงได้แบบซ้อนกันซึ่งมีชื่อIและKพารามิเตอร์ประเภทดังนั้นชื่อเนมสเปซหรือชนิดจะอ้างถึงสิ่งนั้น ประเภทที่สร้างขึ้นด้วยอาร์กิวเมนต์ประเภทที่กำหนด [บิงโก!]
  • หากขั้นตอนก่อนหน้านี้ไม่ประสบความสำเร็จสำหรับแต่ละเนมสเปซNเริ่มต้นด้วยเนมสเปซที่เนมสเปซหรือชนิด - ชื่อเกิดขึ้นดำเนินการต่อด้วยเนมสเปซที่ปิดล้อมแต่ละอัน (ถ้ามี) และลงท้ายด้วยเนมสเปซส่วนกลางขั้นตอนต่อไปนี้จะได้รับการประเมิน จนกว่าเอนทิตีจะตั้งอยู่:
    • ถ้าKเป็นศูนย์และIเป็นชื่อของเนมสเปซในแสดงNว่า ... [ใช่นั่นจะสำเร็จ]

ดังนั้นสัญลักษณ์แสดงหัวข้อย่อยสุดท้ายคือสิ่งที่เลือกSparta คลาสหากสัญลักษณ์แสดงหัวข้อย่อยแรกไม่พบอะไรเลย ... แต่เมื่อคลาสพื้นฐานPlaceกำหนดอินเทอร์เฟซSpartaจะพบก่อนที่เราจะพิจารณาSpartaคลาส

โปรดทราบว่าถ้าคุณทำชนิดที่ซ้อนกันPlace.Spartaชั้นมากกว่าอินเตอร์เฟซก็ยังคงรวบรวมและผลตอบแทนfalse- แต่ปัญหาคอมไพเลอร์เตือนเพราะมันรู้ว่าเป็นตัวอย่างของไม่เคยจะเป็นตัวอย่างของการเรียนSparta Place.Spartaในทำนองเดียวกันหากคุณเก็บPlace.Spartaอินเทอร์เฟซไว้ แต่สร้างSpartaคลาสsealedคุณจะได้รับคำเตือนเนื่องจากไม่มีSpartaอินสแตนซ์ใดที่สามารถใช้อินเทอร์เฟซได้


2
อื่นสังเกตสุ่ม: การใช้เดิมSpartaระดับผลตอบแทนthis is Place trueอย่างไรก็ตามการเพิ่มpublic interface Place { }ไปยังSpartaระดับที่ทำให้เกิดการกลับมาthis is Place falseทำให้หัวของฉันหมุน
budi

@budi: ใช่แล้วเพราะอีกครั้งสัญลักษณ์แสดงหัวข้อก่อนหน้าพบว่าPlaceเป็นอินเทอร์เฟซ
Jon Skeet

22

เมื่อแก้ไขชื่อเป็นค่า "ความใกล้ชิด" ของคำจำกัดความจะถูกใช้เพื่อแก้ไขความคลุมเครือ คำจำกัดความที่ "ใกล้เคียงที่สุด" คือสิ่งที่ถูกเลือก

อินเทอร์เฟซSpartaถูกกำหนดภายในคลาสพื้นฐาน คลาสSpartaถูกกำหนดไว้ในเนมสเปซที่มี สิ่งที่กำหนดไว้ในคลาสพื้นฐานนั้น "ใกล้" กว่าสิ่งที่กำหนดไว้ในเนมสเปซเดียวกัน


1
และลองนึกดูว่าการค้นหาชื่อไม่ได้ผลด้วยวิธีนี้หรือไม่ จากนั้นรหัสการทำงานที่มีคลาสภายในจะเสียหากใครบังเอิญเพิ่มคลาสระดับบนสุดที่มีชื่อเดียวกัน
dan04

1
@ dan04: แต่กลับกันรหัสการทำงานที่ไม่มีคลาสที่ซ้อนกันจะใช้งานไม่ได้หากใครบังเอิญเพิ่มคลาสที่ซ้อนกันที่มีชื่อเดียวกันกับคลาสระดับบนสุด ดังนั้นจึงไม่ใช่สถานการณ์ที่ "ชนะ" ทั้งหมด
Jon Skeet

1
@JonSkeet ฉันจะบอกว่าการเพิ่มคลาสที่ซ้อนกันดังกล่าวเป็นการเปลี่ยนแปลงในพื้นที่ที่รหัสการทำงานมีเหตุผลที่สมเหตุสมผลที่จะได้รับผลกระทบและเฝ้าดูการเปลี่ยนแปลง การเพิ่มคลาสระดับบนสุดที่ไม่เกี่ยวข้องโดยสิ้นเชิงจะยิ่งลบออกไปมาก
Angew ไม่ภูมิใจใน SO

2
@JonSkeet นั่นไม่ใช่แค่ปัญหา Brittle Base Class ในรูปแบบที่แตกต่างกันเล็กน้อยหรือไม่?
João Mendes

1
@ JoãoMendes: ใช่สวยมาก
Jon Skeet

1

คำถามสวย! ฉันต้องการเพิ่มคำอธิบายที่ยาวขึ้นเล็กน้อยสำหรับผู้ที่ไม่ได้ทำ C # ในแต่ละวัน ... เนื่องจากคำถามนี้เป็นข้อเตือนใจที่ดีเกี่ยวกับปัญหาการแก้ปัญหาชื่อโดยทั่วไป

ใช้รหัสเดิมแก้ไขเล็กน้อยด้วยวิธีต่อไปนี้:

  • มาพิมพ์ชื่อประเภทแทนการเปรียบเทียบเหมือนในนิพจน์ดั้งเดิม (เช่นreturn this is Sparta)
  • มากำหนดอินเทอร์เฟซAthenaในPlaceซูเปอร์คลาสเพื่อแสดงความละเอียดของชื่ออินเทอร์เฟซ
  • ลองพิมพ์ชื่อประเภทthisที่ถูกผูกไว้ในSpartaคลาสด้วยเพื่อให้ทุกอย่างชัดเจน

รหัสมีลักษณะดังนี้:

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

ตอนนี้เราสร้างSpartaวัตถุและเรียกใช้วิธีการทั้งสาม

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

ผลลัพธ์ที่เราได้รับคือ:

Sparta
Athena
Place+Athena

อย่างไรก็ตามหากเราแก้ไขคลาส Place และกำหนดอินเทอร์เฟซ Sparta:

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

จากนั้นก็คือสิ่งนี้Sparta- อินเทอร์เฟซ - ที่จะพร้อมใช้งานก่อนสำหรับกลไกการค้นหาชื่อและผลลัพธ์ของรหัสของเราจะเปลี่ยนเป็น:

Sparta
Place+Sparta
Place+Athena

ดังนั้นเราจึงสับสนอย่างมีประสิทธิภาพกับการเปรียบเทียบประเภทใน MakeItReturnFalseนิยามฟังก์ชันเพียงแค่กำหนดอินเทอร์เฟซ Sparta ในซูเปอร์คลาสซึ่งพบได้ก่อนโดยการแก้ชื่อ

แต่เหตุใด C # จึงเลือกจัดลำดับความสำคัญของอินเทอร์เฟซที่กำหนดไว้ใน superclass ในการแก้ปัญหาชื่อ @JonSkeet รู้! และถ้าคุณอ่านคำตอบของเขาคุณจะได้รับรายละเอียดของโปรโตคอลการแก้ไขชื่อใน C #

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.