ทำไมโครงสร้างและคลาสแยกแนวคิดใน C #


44

ในขณะที่การเขียนโปรแกรมใน C # ฉันสะดุดกับการตัดสินใจออกแบบภาษาแปลก ๆ ที่ฉันไม่เข้าใจ

ดังนั้น C # (และ CLR) มีชนิดข้อมูลรวมสองประเภท: struct(ค่าประเภทเก็บไว้ในสแต็กไม่มีการสืบทอด) และclass(อ้างอิงประเภทเก็บไว้ในกองมีมรดก)

การตั้งค่านี้ฟังดูดีในตอนแรก แต่จากนั้นคุณสะดุดวิธีที่ใช้พารามิเตอร์ของชนิดรวมและเพื่อหาว่าจริง ๆ แล้วเป็นประเภทค่าหรือประเภทอ้างอิงคุณต้องค้นหาการประกาศของประเภทนั้น บางครั้งอาจทำให้สับสนได้

วิธีแก้ปัญหาที่เป็นที่ยอมรับโดยทั่วไปดูเหมือนว่าจะประกาศstructว่า "ไม่เปลี่ยนรูป" (ตั้งค่าเขตข้อมูลไว้readonly) เพื่อป้องกันความผิดพลาดที่อาจเกิดขึ้นโดย จำกัดstructประโยชน์ของ

ตัวอย่างเช่น C ++ ใช้โมเดลที่สามารถใช้งานได้มากขึ้น: ช่วยให้คุณสร้างอินสแตนซ์ของวัตถุบนสแต็กหรือบนฮีปและส่งผ่านตามค่าหรือโดยการอ้างอิง (หรือโดยตัวชี้) ฉันได้ยินอยู่เสมอว่า C # ได้รับแรงบันดาลใจจาก C ++ และฉันก็ไม่เข้าใจว่าทำไมมันไม่ใช้เทคนิคนี้ การรวมclassและสร้างstructเป็นหนึ่งเดียวกับตัวเลือกการจัดสรรที่แตกต่างกันสองตัว (ฮีปและสแต็ค) และส่งต่อให้เป็นค่าหรือ (อย่างชัดเจน) เนื่องจากการอ้างอิงผ่านทางrefและoutคำหลักดูเหมือนเป็นสิ่งที่ดี

คำถามคือทำไมclassและstructกลายเป็นแนวคิดที่แยกต่างหากใน C # และ CLR แทนที่จะเป็นประเภทรวมหนึ่งที่มีสองตัวเลือกการจัดสรร?


34
"วิธีการแก้ปัญหาที่เป็นที่ยอมรับโดยทั่วไปดูเหมือนว่าจะประกาศโครงสร้างทั้งหมดว่า" ไม่เปลี่ยนรูป "... การ จำกัด ประโยชน์ของโครงสร้าง" หลายคนอาจโต้แย้งว่าการทำสิ่งใดที่ไม่เปลี่ยนรูปแบบโดยทั่วไปทำให้มีประโยชน์มากขึ้นทุกครั้ง นอกจากนี้structs จะไม่ถูกเก็บไว้ในกองเสมอ พิจารณาวัตถุที่มีstructเขตข้อมูล นอกเหนือจากนั้นขณะที่ Mason Wheeler พูดถึงปัญหาการแบ่งอาจเป็นเหตุผลที่ดีที่สุด
Doval

7
ไม่เป็นความจริงที่ C # ได้รับแรงบันดาลใจจาก C ++; ค่อนข้าง C # ได้รับแรงบันดาลใจจากความผิดพลาด (ตั้งใจดีและมีเสียงดีในเวลาทั้งหมด) ในการออกแบบทั้ง C ++ และ Java
Pieter Geerkens

18
หมายเหตุ: stack และ heap เป็นรายละเอียดการใช้งาน ไม่มีอะไรที่บอกว่าอินสแตนซ์ struct ต้องถูกจัดสรรบนสแต็กและอินสแตนซ์ของคลาสต้องถูกจัดสรรบนฮีป และในความเป็นจริงไม่ได้เป็นจริง ตัวอย่างเช่นมีความเป็นไปได้อย่างมากที่คอมไพเลอร์อาจพิจารณาใช้ Escape Analysis ซึ่งตัวแปรไม่สามารถหลีกเลี่ยงขอบเขตโลคัลและดังนั้นจึงจัดสรรบนสแต็กแม้ว่าจะเป็นอินสแตนซ์ของคลาส ไม่ได้บอกว่าจะต้องมีกองหรือกองเลย คุณสามารถจัดสรรเฟรมสภาพแวดล้อมเป็นรายการที่เชื่อมโยงบนฮีปและไม่มีแม้แต่สแต็กเลย
Jörg W Mittag

5
เธซเธฑเอริค Lippert เกี่ยวกับความเชื่อที่ผิดว่าระบบการพิมพ์มีอะไรที่จะทำอย่างไรกับกลยุทธ์การจัดสรรการจัดเก็บข้อมูล
Zev Spitz

3
"แต่เมื่อคุณสะดุดกับวิธีการรับพารามิเตอร์ของชนิดรวมและเพื่อหาว่ามันเป็นประเภทค่าหรือประเภทอ้างอิงคุณต้องค้นหาการประกาศของประเภท" Umm ทำไมมันสำคัญ ? วิธีการขอให้คุณส่งค่า คุณผ่านค่า คุณต้องสนใจว่าประเด็นนี้เป็นประเภทการอ้างอิงหรือประเภทค่าหรือไม่
Luaan

คำตอบ:


58

เหตุผล C # (และ Java และภาษา OO อื่น ๆ ที่พัฒนาขึ้นหลังจาก C ++) ไม่ได้คัดลอกโมเดลของ C ++ ในด้านนี้เนื่องจากวิธี C ++ เป็นระเบียบที่น่ากลัว

คุณระบุประเด็นที่เกี่ยวข้องด้านบนได้อย่างถูกต้องstruct:: ประเภทค่าไม่มีการสืบทอด class: ประเภทการอ้างอิงมีการสืบทอด การสืบทอดและประเภทของค่า (หรือมากกว่านั้นโดยเฉพาะความหลากหลายและการส่งต่อค่า) ไม่ได้ผสมกัน ถ้าคุณส่งอ็อบเจกต์ประเภทDerivedไปยังเมธอดอาร์กิวเมนต์ประเภทBaseและจากนั้นเรียกใช้เมธอดเสมือนบนมันวิธีเดียวที่จะได้รับพฤติกรรมที่เหมาะสมคือเพื่อให้แน่ใจว่าสิ่งที่ผ่านไปนั้นคือการอ้างอิง

ระหว่างสิ่งนั้นกับความยุ่งเหยิงอื่น ๆ ทั้งหมดที่คุณพบใน C ++ โดยมีวัตถุที่สืบทอดได้ในรูปแบบของค่า (คัดลอกคอนสตรัคเตอร์และการแบ่งส่วนวัตถุมาเป็นใจ!) ทางออกที่ดีที่สุดคือ Just Say No.

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


29
นี่เป็นเพียงอีกหนึ่งการพูดจาโผงผางที่ไม่มีจุดหมายใน C ++ ลงคะแนนไม่ได้ แต่ถ้าทำได้
Bartek Banachewicz

17
@MasonWheeler: " เป็นระเบียบที่น่ากลัว " เสียงส่วนตัวพอ สิ่งนี้ถูกกล่าวถึงแล้วในกระทู้ความคิดเห็นที่ยาวเพื่อคำตอบของคุณอีกคนหนึ่ง ; ด้ายถูกอบ (น่าเสียดายเพราะมันมีความคิดเห็นที่เป็นประโยชน์แม้ว่าในซอสสงครามไฟ) ฉันไม่คิดว่ามันคุ้มค่าที่จะทำซ้ำสิ่งทั้งหมดที่นี่ แต่ "C # เข้าใจถูกและ C ++ เข้าใจผิด" (ซึ่งดูเหมือนจะเป็นข้อความที่คุณพยายามสื่อถึง) เป็นคำแถลงส่วนตัว
Andy Prowl

15
@ MasonWheeler: ฉันทำในหัวข้อที่ได้รับการอบและอื่น ๆ อีกหลายคน - ซึ่งเป็นเหตุผลที่ฉันคิดว่ามันโชคร้ายที่มันถูกลบ ฉันไม่คิดว่ามันเป็นความคิดที่ดีที่จะทำซ้ำเธรดที่นี่ แต่รุ่นสั้นคือ: ใน C ++ ผู้ใช้ประเภทไม่ใช่ผู้ออกแบบจะต้องตัดสินใจด้วยซีแมนติกประเภทใดที่ควรใช้ (ซีแมนทิกส์อ้างอิงหรือค่า ความหมาย) นี้มีข้อดีและข้อเสีย: คุณโกรธกับข้อเสียโดยไม่คำนึงถึง (หรือรู้?) ข้อดี นั่นเป็นเหตุผลที่การวิเคราะห์เป็นอัตนัย
Andy Prowl

7
ถอนหายใจผมเชื่อว่าการอภิปรายการละเมิด LSP ได้เกิดขึ้นแล้ว และผมค่อนข้างเชื่อว่าคนส่วนใหญ่เห็นพ้องกันว่าการพูดถึง LSP สวยแปลกและไม่เกี่ยวข้อง แต่ไม่สามารถตรวจสอบเพราะพอควรอบแสดงความคิดเห็นหัวข้อ
Bartek Banachewicz

9
หากคุณย้ายย่อหน้าสุดท้ายของคุณไปด้านบนและลบย่อหน้าแรกปัจจุบันฉันคิดว่าคุณมีอาร์กิวเมนต์ที่สมบูรณ์แบบ แต่ย่อหน้าแรกปัจจุบันเป็นเพียงความคิดเห็น
Martin York

19

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

C ++ โดยการเปรียบเทียบไม่เพียงรวมถึงประแจและคีมที่ปรับได้ แต่เครื่องมือที่มีวัตถุประสงค์พิเศษบางอย่างที่แปลกประหลาดซึ่งมีจุดประสงค์ไม่ชัดเจนในทันทีและหากคุณไม่ทราบวิธีที่ถูกต้องในการจับพวกเขา นิ้วหัวแม่มือ (แต่เมื่อคุณเข้าใจวิธีใช้งานแล้วสามารถทำสิ่งต่าง ๆ ที่เป็นไปไม่ได้โดยใช้เครื่องมือพื้นฐานในกล่องเครื่องมือ C #) นอกจากนี้ยังมีเครื่องกลึงเครื่องกัดเครื่องบดพื้นผิวเลื่อยวงเดือนตัดโลหะ ฯลฯ เพื่อให้คุณสามารถออกแบบและสร้างเครื่องมือใหม่ ๆ ได้ทุกเมื่อที่คุณต้องการ (แต่ใช่เครื่องมือของช่างเครื่องเหล่านี้จะทำให้เกิด การบาดเจ็บสาหัสหากคุณไม่รู้ว่าคุณกำลังทำอะไรอยู่กับพวกเขา - หรือแม้ว่าคุณเพิ่งจะประมาท)

สิ่งนั้นสะท้อนถึงความแตกต่างพื้นฐานในปรัชญา: C ++ พยายามให้เครื่องมือทั้งหมดที่คุณอาจต้องการสำหรับการออกแบบที่คุณต้องการ มันแทบจะไม่พยายามควบคุมวิธีที่คุณใช้เครื่องมือเหล่านั้นดังนั้นมันจึงใช้งานง่ายในการสร้างงานออกแบบที่ทำงานได้ดีในสถานการณ์ที่หายากรวมถึงงานออกแบบที่อาจเป็นเพียงความคิดที่ไม่มีหมัดและไม่มีใครรู้ถึงสถานการณ์ที่ พวกเขามีแนวโน้มที่จะทำงานได้ดี โดยเฉพาะอย่างยิ่งข้อตกลงนี้ทำโดยการแยกการตัดสินใจออกแบบออกแม้กระทั่งในทางปฏิบัติจริง ๆ แล้วจะเชื่อมโยงกันเกือบตลอดเวลา เป็นผลให้มีความแตกต่างอย่างมากระหว่างเพียงแค่เขียน C ++ และเขียน C ++ ได้ดี ในการเขียนภาษาซีพลัสพลัสคุณจำเป็นต้องรู้สำนวนและกฎของหัวแม่มือมากมาย (รวมถึงกฎของหัวแม่มือเกี่ยวกับวิธีพิจารณาอย่างจริงจังก่อนที่จะทำลายกฎของหัวแม่มืออื่น ๆ ) ผลที่ตามมา, C ++ มุ่งเน้นไปที่การใช้งานง่าย (โดยผู้เชี่ยวชาญ) มากกว่าการเรียนรู้ที่ง่าย นอกจากนี้ยังมีสถานการณ์ (มากเกินไป) ซึ่งมันก็ไม่ได้ใช้งานง่ายนัก

C # จะมากขึ้นเพื่อพยายามที่จะมีผลบังคับใช้ (หรืออย่างน้อยมากขอแนะนำ) สิ่งที่นักออกแบบภาษาที่ถือว่าการออกแบบที่ดี มีบางสิ่งที่แยกออกมาในภาษา C ++ (แต่โดยทั่วไปแล้วในทางปฏิบัติ) จะเชื่อมต่อกันโดยตรงใน C # มันอนุญาตให้ใช้รหัส "ไม่ปลอดภัย" เพื่อผลักดันขอบเขตเล็กน้อย แต่โดยสุจริตไม่มาก

ผลที่ได้คือในมือข้างหนึ่งมีการออกแบบค่อนข้างน้อยที่สามารถแสดงออกได้โดยตรงใน C ++ ที่มีความชัดเจนในการแสดงออกใน C # ในทางกลับกันก็เป็นทั้งง่ายมากที่จะเรียนรู้ C # และมีโอกาสในการผลิตการออกแบบที่น่ากลัวจริงๆที่จะไม่ทำงานกับสถานการณ์ของคุณ (หรืออาจอื่น ๆ ) เป็นอย่างมากลดลง ในหลาย ๆ กรณี (อาจเป็นไปได้มากที่สุด) คุณสามารถออกแบบให้ใช้งานได้ง่ายเพียงแค่ "ไปตามกระแส" เพื่อพูด หรืออย่างหนึ่งในเพื่อนของฉัน (อย่างน้อยฉันก็ชอบคิดว่าเขาเป็นเพื่อน - ไม่แน่ใจว่าเขาเห็นด้วยหรือไม่) ชอบที่จะใส่ C # ทำให้มันง่ายที่จะตกอยู่ในหลุมแห่งความสำเร็จ

ดังนั้นเมื่อดูคำถามโดยเฉพาะเกี่ยวกับวิธีclassและstructวิธีการใช้ภาษาทั้งสอง: วัตถุที่สร้างขึ้นในลำดับชั้นการสืบทอดที่คุณอาจใช้วัตถุของคลาสที่ได้รับมาในหน้ากากของคลาส / อินเทอร์เฟซพื้นฐานของคุณ ติดอยู่กับความจริงที่ว่าคุณต้องทำผ่านตัวชี้หรือการอ้างอิงในระดับที่เป็นรูปธรรมสิ่งที่เกิดขึ้นคือวัตถุของคลาสที่ได้รับมามีหน่วยความจำบางอย่างที่สามารถใช้เป็นตัวอย่างของคลาสพื้นฐาน / อินเตอร์เฟซและวัตถุที่ได้รับจะถูกจัดการผ่านที่อยู่ของหน่วยความจำส่วนนั้น

ใน C ++ มันขึ้นอยู่กับโปรแกรมเมอร์ที่จะทำอย่างถูกต้อง - เมื่อเขาใช้การสืบทอดมันขึ้นอยู่กับเขาเพื่อให้แน่ใจว่า (ตัวอย่าง) ฟังก์ชันที่ทำงานกับคลาส polymorphic ในลำดับชั้นทำได้โดยใช้ตัวชี้หรืออ้างอิงถึงฐาน ชั้น

ใน C # สิ่งที่เป็นพื้นฐานการแยกกันระหว่างประเภทที่ชัดเจนมากขึ้นและบังคับใช้โดยภาษาเอง โปรแกรมเมอร์ไม่จำเป็นต้องทำตามขั้นตอนใด ๆ ในการส่งอินสแตนซ์ของคลาสโดยการอ้างอิงเนื่องจากจะเกิดขึ้นตามค่าเริ่มต้น


2
ในฐานะแฟน C ++ ฉันคิดว่านี่เป็นบทสรุปที่ยอดเยี่ยมของความแตกต่างระหว่าง C # และ Chainsaw ของกองทัพสวิส
David Thornley

1
@ DavidThornley: อย่างน้อยฉันก็พยายามที่จะเขียนสิ่งที่ฉันคิดว่าจะเป็นการเปรียบเทียบที่ค่อนข้างสมดุล ไม่ใช่นิ้วชี้ แต่บางสิ่งที่ฉันเห็นเมื่อฉันเขียนสิ่งนี้ทำให้ฉันรู้สึกว่า ... ค่อนข้างไม่ถูกต้อง
Jerry Coffin

7

มาจาก"C #: ทำไมเราต้องการภาษาอื่น" - Gunnerson, Eric:

ความเรียบง่ายเป็นเป้าหมายการออกแบบที่สำคัญสำหรับ C #

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

[ ... ]

ประเภทค่ามากไปประกอบการและการแปลงที่ผู้ใช้กำหนดทุกความซับซ้อนเพิ่มภาษาแต่ช่วยให้สถานการณ์ของผู้ใช้ที่สำคัญที่จะทำให้ง่ายขึ้นอย่างมาก

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

อะไรจะดีไปกว่าการแยกวัตถุที่สกปรกน่าเกลียดและไม่ดีออกไปซึ่งมีค่าความหมายภายใต้แท็กของstruct?


1
ฉันไม่รู้ว่าอาจจะไม่ใช้วัตถุที่สกปรกน่าเกลียดและไม่ดีที่มีการอ้างอิงความหมายใช่ไหม
Bartek Banachewicz

บางที ... ฉันหลงทาง
manlio

2
IMHO หนึ่งในข้อบกพร่องที่ใหญ่ที่สุดในการออกแบบ Java คือการขาดวิธีการใด ๆ ในการประกาศว่าตัวแปรนั้นถูกใช้เพื่อห่อหุ้มตัวตนหรือความเป็นเจ้าของหรือไม่และหนึ่งในข้อบกพร่องที่ใหญ่ที่สุดใน C # คือการขาดวิธีการ ตัวแปรจากการดำเนินการบนวัตถุที่ตัวแปรถือการอ้างอิง ถึงแม้ว่า Runtime จะไม่สนใจความแตกต่างดังกล่าว แต่ก็สามารถระบุในภาษาว่าตัวแปรชนิดint[]ควรแบ่งใช้หรือเปลี่ยนแปลงได้ (อาร์เรย์สามารถเป็นได้ แต่โดยทั่วไปไม่ใช่ทั้งคู่) จะช่วยทำให้โค้ดผิดดูผิด
supercat

4

แทนที่จะคิดถึงประเภทของค่าที่ได้รับObjectมันจะมีประโยชน์มากขึ้นถ้าคิดถึงประเภทของที่เก็บข้อมูลที่มีอยู่ในเอกภพที่แยกต่างหากทั้งหมดจากประเภทอินสแตนซ์ของชั้นเรียน แต่สำหรับทุกประเภทของค่าจะมีประเภทของวัตถุ ที่เก็บสินค้าประเภทโครงสร้างนั้นมีการเชื่อมต่อกันของฟิลด์สาธารณะและส่วนตัวของประเภทนั้นและประเภทฮีปจะถูกสร้างขึ้นโดยอัตโนมัติตามรูปแบบเช่น:

// Defined structure
struct Point : IEquatable<Point>
{
  public int X,Y;
  public Point(int x, int y) { X=x; Y=y; }
  public bool Equals(Point other) { return X==other.X && y==other.Y; }
  public bool Equals(Object other)
  { return other != null && other.GetType()==typeof(this) && Equals(Point(other)); }
  public bool ToString() { return String.Format("[{0},{1}", x, y); }
  public bool GetHashCode() { return unchecked(x+y*65531); }
}        
// Auto-generated class
class boxed_Point: IEquatable<Point>
{
  public Point value; // Fake name; C++/CLI, though not C#, allow full access
  public boxed_Point(Point v) { value=v; }
  // Members chain to each member of the original
  public bool Equals(Point other) { return value.Equals(other); }
  public bool Equals(Object other) { return value.Equals(other); }
  public String ToString() { return value.ToString(); }
  public Int32 GetHashCode() { return value.GetHashCode(); }
}

และสำหรับข้อความสั่งเช่น: Console.WriteLine ("ค่าคือ {0}", somePoint);

ที่จะแปลเป็น: boxed_Point box1 = new boxed_Point ใหม่ (somePoint); Console.WriteLine ("ค่าคือ {0}", box1);

ในทางปฏิบัติเพราะประเภทสถานที่เก็บและประเภทเช่นกองอยู่ในจักรวาลที่แยกจากกันมันไม่จำเป็นที่จะเรียกประเภทกองอินสแตนซ์ในสิ่งที่ชอบเช่นboxed_Int32; เนื่องจากระบบจะทราบว่าบริบทใดที่ต้องการอินสแตนซ์ของฮีป - ออบเจ็กต์และสิ่งใดที่จำเป็นต้องมีที่เก็บข้อมูล

บางคนคิดว่าค่าประเภทใด ๆ ที่ไม่ทำตัวเหมือนวัตถุควรถูกพิจารณาว่าเป็น "ความชั่ว" ฉันใช้มุมมองตรงกันข้าม: เนื่องจากที่เก็บข้อมูลของประเภทค่าไม่ใช่วัตถุหรือการอ้างอิงถึงวัตถุความคาดหวังที่พวกเขาควรทำตัวเหมือนวัตถุควรได้รับการพิจารณาว่าไม่มีประโยชน์ ในกรณีที่ struct สามารถทำงานได้อย่างมีประโยชน์เหมือนวัตถุไม่มีอะไรผิดปกติกับการทำเช่นนั้น แต่structสิ่งที่อยู่ในใจไม่มีอะไรมากไปกว่าการรวมกันของเขตข้อมูลสาธารณะและส่วนตัวที่ติดอยู่กับเทปพันสายไฟ

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