เนื่องจากคุณถามว่าเหตุใด C # จึงทำเช่นนี้จึงเป็นการดีที่สุดที่จะถามผู้สร้าง C # Anders Hejlsberg สถาปนิกนำสำหรับ C #, ตอบว่าทำไมพวกเขาเลือกที่จะไม่ไปกับเสมือนโดยค่าเริ่มต้น (เช่นใน Java) ในการสัมภาษณ์ , ตัวอย่างที่เกี่ยวข้องอยู่ด้านล่าง
โปรดทราบว่า Java มีเสมือนเป็นค่าเริ่มต้นด้วยคำหลักสุดท้ายเพื่อทำเครื่องหมายวิธีการที่ไม่ใช่เสมือน ยังคงเป็นสองแนวคิดในการเรียนรู้ แต่คนจำนวนมากไม่ทราบเกี่ยวกับคำหลักสุดท้ายหรือไม่ใช้เชิงรุก C # บังคับให้ใช้ virtual และ new / override เพื่อทำการตัดสินใจอย่างมีสติ
มีสาเหตุหลายประการ หนึ่งคือประสิทธิภาพการทำงาน เราสามารถสังเกตได้ว่าในขณะที่คนเขียนโค้ดใน Java พวกเขาลืมที่จะทำเครื่องหมายวิธีการของพวกเขาเป็นครั้งสุดท้าย ดังนั้นวิธีการเหล่านั้นเป็นเสมือน เพราะพวกเขาเสมือนพวกเขาทำงานได้ไม่ดี มีเพียงค่าใช้จ่ายด้านประสิทธิภาพที่เกี่ยวข้องกับการเป็นวิธีเสมือน นั่นเป็นปัญหาหนึ่ง
ปัญหาที่สำคัญมากขึ้นเป็นเวอร์ชัน มีโรงเรียนสองแห่งที่คิดเกี่ยวกับวิธีการเสมือนจริง โรงเรียนวิชาการแห่งความคิดกล่าวว่า "ทุกสิ่งควรเป็นเสมือนจริงเพราะฉันอาจต้องการลบล้างมันสักวันหนึ่ง" โรงเรียนแห่งความคิดที่เน้นการปฏิบัติซึ่งมาจากการสร้างแอปพลิเคชันจริงที่ทำงานในโลกแห่งความเป็นจริงกล่าวว่า "เราต้องระวังอย่างแท้จริงเกี่ยวกับสิ่งที่เราทำเสมือนจริง"
เมื่อเราทำบางสิ่งบางอย่างเสมือนจริงบนแพลตฟอร์มเราทำสัญญาที่น่ากลัวมากมายเกี่ยวกับวิธีการที่มันจะวิวัฒนาการในอนาคต สำหรับวิธีการที่ไม่ใช่เสมือนจริงเราสัญญาว่าเมื่อคุณเรียกวิธีนี้ว่า x และ y จะเกิดขึ้น เมื่อเราเผยแพร่วิธีเสมือนจริงใน API เราไม่เพียง แต่สัญญาว่าเมื่อคุณเรียกวิธีนี้ว่า x และ y จะเกิดขึ้น นอกจากนี้เรายังสัญญาว่าเมื่อคุณแทนที่วิธีนี้เราจะเรียกมันว่าในลำดับนี้โดยเฉพาะเกี่ยวกับวิธีการอื่น ๆ เหล่านี้และรัฐจะอยู่ในนี้และค่าคงที่
ทุกครั้งที่คุณพูดว่า virtual ใน API คุณกำลังสร้าง hook call back ในฐานะนักออกแบบระบบปฏิบัติการเฟรมเวิร์กหรือ API คุณต้องระวังให้ดี คุณไม่ต้องการให้ผู้ใช้เอาชนะและเชื่อมต่อกับจุดใดก็ได้ใน API เพราะคุณไม่สามารถทำตามสัญญาได้ และผู้คนอาจไม่เข้าใจอย่างเต็มที่ถึงคำสัญญาที่พวกเขาทำเมื่อพวกเขาทำสิ่งที่เสมือนจริง
สัมภาษณ์มีการอภิปรายเพิ่มเติมเกี่ยวกับวิธีที่นักพัฒนาคิดเกี่ยวกับการออกแบบการสืบทอดคลาสและวิธีการที่นำไปสู่การตัดสินใจ
ตอนนี้คำถามต่อไปนี้:
ฉันไม่สามารถเข้าใจว่าทำไมในโลกฉันจะเพิ่มวิธีการใน DerivedClass ของฉันที่มีชื่อเดียวกันและลายเซ็นเดียวกันกับ BaseClass และกำหนดพฤติกรรมใหม่ แต่ที่ polymorphism เวลาทำงานเมธอด BaseClass จะถูกเรียก! (ซึ่งไม่ใช่การเอาชนะ แต่ควรมีเหตุผล)
นี่จะเป็นเมื่อคลาสที่ได้รับต้องการประกาศว่าไม่ปฏิบัติตามสัญญาของคลาสพื้นฐาน แต่มีเมธอดที่มีชื่อเดียวกัน (สำหรับทุกคนที่ไม่ทราบความแตกต่างระหว่างnew
และoverride
ใน C # ดูหน้า MSDN นี้ )
สถานการณ์จริงมากคือ:
- คุณสร้าง API
Vehicle
ซึ่งมีระดับที่เรียกว่า
- ฉันเริ่มใช้ API ของคุณและได้รับ
Vehicle
แล้ว
- คุณชั้นไม่ได้มีวิธีการใด
Vehicle
ๆPerformEngineCheck()
- ในของฉันเรียนฉันจะเพิ่มวิธีการ
Car
PerformEngineCheck()
- คุณเปิดตัวรุ่นใหม่ของ API
PerformEngineCheck()
ของคุณและเพิ่ม
- ฉันไม่สามารถเปลี่ยนชื่อวิธีการของฉันเพราะลูกค้าของฉันขึ้นอยู่กับ API ของฉันและมันจะทำลายพวกเขา
ดังนั้นเมื่อฉันคอมไพล์ใหม่ API ของคุณ C # เตือนฉันเรื่องนี้เช่น
หากฐานPerformEngineCheck()
ไม่ใช่virtual
:
app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
Use the new keyword if hiding was intended.
และถ้าฐานPerformEngineCheck()
เป็นvirtual
:
app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
ตอนนี้ฉันต้องตัดสินใจอย่างชัดเจนว่าคลาสของฉันขยายสัญญาคลาสพื้นฐานจริง ๆ หรือว่าเป็นสัญญาอื่น แต่เกิดขึ้นเป็นชื่อเดียวกัน
- ด้วยการทำให้มัน
new
ฉันจะไม่ทำลายลูกค้าของฉันหากการทำงานของวิธีการพื้นฐานแตกต่างจากวิธีที่ได้รับ รหัสอ้างอิงใด ๆ ที่Vehicle
จะไม่เห็นCar.PerformEngineCheck()
เรียก แต่รหัสที่มีการอ้างอิงถึงจะยังคงเห็นการทำงานเดียวกันกับที่ผมได้นำเสนอในCar
PerformEngineCheck()
ตัวอย่างที่คล้ายกันคือเมื่อเมธอดอื่นในคลาสฐานอาจเรียกใช้PerformEngineCheck()
(โดยเฉพาะในเวอร์ชันที่ใหม่กว่า) วิธีหนึ่งจะป้องกันไม่ให้เรียกPerformEngineCheck()
คลาสที่ได้รับมาได้อย่างไร ใน Java การตัดสินใจนั้นจะอยู่กับคลาสฐาน แต่ก็ไม่รู้อะไรเลยเกี่ยวกับคลาสที่ได้รับมา ใน C # การตัดสินใจนั้นขึ้นอยู่กับทั้งคลาสพื้นฐาน (ผ่านvirtual
คำหลัก) และกับคลาสที่ได้รับ (ผ่านทางnew
และoverride
คำหลัก)
แน่นอนข้อผิดพลาดที่คอมไพเลอร์โยนยังเป็นเครื่องมือที่มีประโยชน์สำหรับโปรแกรมเมอร์ที่จะไม่ทำให้เกิดข้อผิดพลาดโดยไม่คาดคิด (เช่นแทนที่หรือให้ฟังก์ชั่นใหม่โดยไม่ทราบ)
เช่นเดียวกับ Anders พูดโลกแห่งความจริงบังคับให้เราเข้าไปในประเด็นดังกล่าวซึ่งถ้าเราต้องเริ่มจากศูนย์เราจะไม่ต้องการเข้าไป
แก้ไข: เพิ่มตัวอย่างว่าnew
จะต้องใช้ที่ใดเพื่อให้แน่ใจว่าสามารถใช้งานร่วมกับอินเตอร์เฟสได้
แก้ไข: ในขณะที่ผ่านความคิดเห็นฉันยังเจอบทความโดย Eric Lippert (จากนั้นหนึ่งในสมาชิกของคณะกรรมการออกแบบ C #) ในสถานการณ์ตัวอย่างอื่น ๆ (กล่าวถึงโดย Brian)
ส่วนที่ 2: อิงตามคำถามที่อัปเดต
แต่ในกรณีของ C # ถ้า SpaceShip ไม่ได้แทนที่ความเร็วของยานพาหนะคลาส 'เร่งความเร็วและใช้ใหม่แล้วตรรกะของรหัสของฉันจะถูกทำลาย นั่นเป็นข้อเสียหรือไม่?
ใครเป็นผู้ตัดสินใจว่าSpaceShip
จริง ๆ แล้วจะเอาชนะVehicle.accelerate()
หรือแตกต่างกันอย่างไร มันจะต้องมีการSpaceShip
พัฒนา ดังนั้นหากSpaceShip
นักพัฒนาตัดสินใจว่าพวกเขาไม่ได้รักษาสัญญาของคลาสพื้นฐานการโทรของคุณไปที่Vehicle.accelerate()
ไม่ควรไปSpaceShip.accelerate()
หรือควร? new
นั่นคือเมื่อพวกเขาจะทำเครื่องหมายว่า override
แต่ถ้าพวกเขาตัดสินใจว่ามันไม่แน่นอนให้สัญญาแล้วพวกเขาจะในความเป็นจริงทำเครื่องหมาย ในทั้งสองกรณีรหัสของคุณจะทำงานได้อย่างถูกต้องโดยการเรียกวิธีการที่ถูกต้องอยู่บนพื้นฐานของการทำสัญญา รหัสของคุณจะตัดสินใจได้อย่างไรว่าSpaceShip.accelerate()
จริง ๆ แล้วมีการเอาชนะVehicle.accelerate()
หรือเป็นชื่อที่ขัดแย้งกันหรือไม่ (ดูตัวอย่างของฉันด้านบน)
อย่างไรก็ตามในกรณีของมรดกนัยถึงแม้ว่าSpaceShip.accelerate()
ไม่ได้ให้สัญญาของวิธีการเรียกยังจะไปVehicle.accelerate()
SpaceShip.accelerate()