กระบวนการออกแบบทั้งหมดส่งผลให้เกิดการประนีประนอมระหว่างเป้าหมายที่เข้ากันไม่ได้ น่าเสียดายที่กระบวนการออกแบบสำหรับ&&
ผู้ดำเนินการที่มีการโอเวอร์โหลดใน C ++ นั้นสร้างผลลัพธ์ที่น่าสับสน: ซึ่งจะไม่มีการแสดงคุณลักษณะที่คุณต้องการจาก&&
พฤติกรรมการลัดวงจรที่สั้นมาก
รายละเอียดว่ากระบวนการออกแบบนั้นสิ้นสุดลงอย่างไรในสถานที่ที่โชคร้ายเหล่านี้ที่ฉันไม่รู้ อย่างไรก็ตามมีความเกี่ยวข้องเพื่อดูว่ากระบวนการออกแบบในภายหลังนำผลลัพธ์ที่ไม่พึงประสงค์นี้มาพิจารณาอย่างไร ใน C # ตัว&&
ดำเนินการที่โอเวอร์โหลดคือการลัดวงจร นักออกแบบของ C # ประสบความสำเร็จได้อย่างไร
หนึ่งในคำตอบอื่น ๆ ชี้ให้เห็น "แลมบ์ดายก" นั่นคือ:
A && B
สามารถตระหนักได้ว่าเป็นสิ่งที่มีคุณธรรมเทียบเท่ากับ:
operator_&& ( A, ()=> B )
โดยที่อาร์กิวเมนต์ที่สองใช้กลไกบางอย่างสำหรับการประเมินผลแบบขี้เกียจดังนั้นเมื่อประเมินผลข้างเคียงและมูลค่าของนิพจน์จะถูกสร้างขึ้น การใช้งานโอเปอเรเตอร์ที่โอเวอร์โหลดจะทำการประเมินแบบเลหลังเมื่อจำเป็นเท่านั้น
นี่ไม่ใช่สิ่งที่ทีมออกแบบ C # ทำ (นอกเหนือจาก: แม้ว่าการยกแลมบ์ดาเป็นสิ่งที่ฉันทำเมื่อถึงเวลาที่ต้องทำนิพจน์การเป็นตัวแทนของ??
โอเปอเรเตอร์ซึ่งต้องการการดำเนินการแปลงบางอย่างที่จะดำเนินการอย่างเกียจคร้านอธิบายว่าในรายละเอียด ใช้งานได้ แต่มีน้ำหนักมากพอที่เราต้องการหลีกเลี่ยง)
แต่วิธีการแก้ปัญหา C # แบ่งปัญหาออกเป็นสองปัญหาที่แยกจากกัน:
- เราควรประเมินตัวถูกดำเนินการทางขวาหรือไม่?
- ถ้าคำตอบข้างต้นคือ "ใช่" แล้วเราจะรวมตัวถูกดำเนินการทั้งสองได้อย่างไร?
ดังนั้นจึงแก้ปัญหาได้โดยทำให้ผิดกฎหมายเกินพิกัด&&
โดยตรง แต่ใน C # คุณต้องโอเวอร์โหลดโอเปอเรเตอร์สองตัวซึ่งแต่ละอันตอบคำถามหนึ่งในสองข้อนั้น
class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }
// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...
(นอกเหนือจาก: จริง ๆ สามข้อ C # ต้องการว่าหากfalse
มีการให้โอเปอเรเตอร์แล้วtrue
ต้องให้โอเปอเรเตอร์ซึ่งตอบคำถาม: สิ่งนี้คือ "true-ish?" โดยทั่วไปแล้วไม่มีเหตุผลที่จะให้โอเปอเรเตอร์เดียว ต้องใช้ทั้งคู่)
พิจารณาข้อความของแบบฟอร์ม:
C cresult = cleft && cright;
คอมไพเลอร์สร้างรหัสสำหรับสิ่งนี้ตามที่คิดว่าคุณเขียน pseudo-C # นี้:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
อย่างที่คุณเห็นด้านซ้ายมือจะถูกประเมินเสมอ ถ้ามันถูกกำหนดให้เป็น "false-ish" มันก็จะเป็นผลลัพธ์ มิฉะนั้นทางด้านขวาจะได้รับการประเมินและผู้ดำเนินการที่ผู้ใช้กำหนดเองกระตือรือร้น&
จะถูกเรียกใช้
ตัว||
ดำเนินการถูกกำหนดในลักษณะที่คล้ายคลึงกันเป็นการเรียกใช้ตัวดำเนินการจริงและตัว|
ดำเนินการกระตือรือร้น:
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
โดยการกำหนดทั้งสี่ผู้ประกอบการ - true
, false
, &
และ|
- C # ช่วยให้คุณไม่เพียง แต่พูดcleft && cright
แต่ยังไม่ใช่ลัดวงจรcleft & cright
และif (cleft) if (cright) ...
และc ? consequence : alternative
และwhile(c)
และอื่น ๆ
ตอนนี้ฉันบอกว่ากระบวนการออกแบบทั้งหมดเป็นผลมาจากการประนีประนอม ที่นี่นักออกแบบภาษา C # ได้รับการลัดวงจร&&
และ||
ถูกต้อง แต่การทำเช่นนั้นต้องใช้ตัวดำเนินการจำนวนมากเกินสี่ตัวแทนที่จะเป็นสองตัวซึ่งบางคนรู้สึกสับสน ผู้ประกอบการคุณสมบัติจริง / เท็จเป็นหนึ่งในคุณสมบัติที่เข้าใจกันน้อยที่สุดใน C # เป้าหมายของการมีภาษาที่เข้าใจง่ายและตรงไปตรงมาซึ่งผู้ใช้ C ++ คุ้นเคยนั้นถูกต่อต้านโดยความปรารถนาที่จะมีการลัดวงจรและความปรารถนาที่จะไม่ใช้การยกแลมบ์ดาหรือการประเมินแบบขี้เกียจในรูปแบบอื่น ๆ ฉันคิดว่านั่นเป็นตำแหน่งการประนีประนอมที่สมเหตุสมผล แต่สิ่งสำคัญคือต้องตระหนักว่ามันเป็นตำแหน่งการประนีประนอม ต่างกันเพียง ประนีประนอมตำแหน่งมากกว่าผู้ออกแบบของ C ++
หากหัวเรื่องของการออกแบบภาษาสำหรับผู้ให้บริการดังกล่าวสนใจคุณให้พิจารณาอ่านซีรี่ส์ของฉันว่าเพราะเหตุใด C # จึงไม่ได้กำหนดโอเปอเรเตอร์เหล่านี้ไว้ใน Booleans ที่ไม่มีค่า:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)