ทำไมการเปลี่ยนแปลงที่ไม่แน่นอนของ“ ความชั่วร้าย”?


485

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

ปัญหาที่เกิดขึ้นจริงกับความไม่แน่นอนและ structs ใน C # คืออะไร?


12
การอ้างถึง structable mutable นั้นเป็นสิ่งที่ชั่วร้ายเหมือนกับการอ้างว่าints, bools และค่าชนิดอื่น ๆ ทั้งหมดนั้นเป็นความชั่วร้าย มีหลายกรณีสำหรับความแปรปรวนและการเปลี่ยนแปลงไม่ได้ กรณีเหล่านี้ขึ้นอยู่กับบทบาทที่เล่นข้อมูลไม่ใช่ประเภทของการจัดสรร / แชร์หน่วยความจำ
Slipp D. Thompson

41
@slipp intและboolมีความไม่แน่นอน ..
Blorgbeard ออก

2
... - .ไวยากรณ์ทำให้การดำเนินการกับข้อมูลที่พิมพ์ซ้ำและข้อมูลที่พิมพ์ตามตัวอักษรดูเหมือนกันแม้ว่ามันจะแตกต่างกันอย่างชัดเจน นี่เป็นความผิดพลาดของคุณสมบัติของ C # ไม่ใช่โครงสร้าง - ภาษาบางภาษาเสนอa[V][X] = 3.14ไวยากรณ์ทางเลือกสำหรับการกลายพันธุ์แบบแทนที่ ใน C # คุณควรจะเสนอวิธีการเปลี่ยนแปลง mut-member แบบสมาชิกเช่น 'MutateV (Action <ref Vector2> Mutator)' และใช้มันเหมือนa.MutateV((v) => { v.X = 3; }) (ตัวอย่างง่ายเกินไปเนื่องจากข้อ จำกัด C # เกี่ยวกับrefคำสำคัญ แต่มีบางอย่าง วิธีการแก้ปัญหาควรจะเป็นไปได้)
Slipp D. Thompson

2
@ Slipp ดีฉันคิดตรงข้ามกับ structs ประเภทนี้ ทำไมคุณถึงคิดว่าโครงสร้างที่นำไปใช้งานแล้วในไลบรารี. NET เช่น DateTime หรือ TimeSpan (ที่คล้ายกัน) จึงไม่เปลี่ยนรูป? อาจเป็นประโยชน์ในการเปลี่ยนแปลงสมาชิกเพียงคนเดียวใน var ของ struct เช่นนี้ แต่มันไม่สะดวกเกินไปนำไปสู่ปัญหาที่มากเกินไป ที่จริงแล้วคุณคิดผิดเกี่ยวกับโปรเซสเซอร์ที่ใช้คำนวณอะไรเนื่องจาก C # ไม่ได้คอมไพล์กับแอสเซมเบลอร์มันจึงรวมเข้ากับ IL ใน IL (หากเรามีชื่อตัวแปรอยู่แล้วx) การดำเนินการเดี่ยวนี้คือ 4 คำสั่ง: ldloc.0(โหลดตัวแปร 0 ดัชนีลงใน ...
Sushi271

1
... พิมพ์ Tเป็นประเภท Ref เป็นเพียงคำหลักที่ทำให้ตัวแปรถูกส่งผ่านไปยังเมธอดเองไม่ใช่สำเนาของมัน นอกจากนี้ยังมีความรู้สึกสำหรับประเภทการอ้างอิงเนื่องจากเราสามารถเปลี่ยนตัวแปรเช่นการอ้างอิงนอกวิธีการจะชี้ไปที่วัตถุอื่น ๆ หลังจากที่มีการเปลี่ยนแปลงภายในวิธีการ เนื่องจากref Tไม่ใช่ประเภท แต่รูปแบบของการส่งผ่านพารามิเตอร์เมธอดคุณไม่สามารถใส่ลงไป<>ได้เพราะสามารถใส่ได้เฉพาะประเภทเท่านั้น ดังนั้นมันจึงไม่ถูกต้อง บางทีมันอาจจะสะดวกในการทำเช่นนั้นบางทีทีมงาน C # อาจทำสิ่งนี้สำหรับบางเวอร์ชั่นใหม่ แต่ตอนนี้พวกเขากำลังทำงานอยู่ ...
Sushi271

คำตอบ:


290

Structs เป็นประเภทค่าซึ่งหมายความว่าจะถูกคัดลอกเมื่อส่งผ่าน

ดังนั้นหากคุณเปลี่ยนสำเนาคุณจะเปลี่ยนเฉพาะสำเนานั้นไม่ใช่ต้นฉบับและไม่ใช่สำเนาอื่น ๆ ที่อาจมีอยู่

หาก struct ของคุณไม่เปลี่ยนรูปสำเนาอัตโนมัติทั้งหมดที่เกิดจากการส่งผ่านตามค่าจะเหมือนกัน

หากคุณต้องการเปลี่ยนคุณต้องทำอย่างมีสติด้วยการสร้างอินสแตนซ์ใหม่ของโครงสร้างด้วยข้อมูลที่แก้ไข (ไม่ใช่สำเนา)


82
"ถ้าโครงสร้างของคุณไม่เปลี่ยนรูปสำเนาทั้งหมดก็จะเหมือนกัน" ไม่หมายความว่าคุณต้องทำสำเนาอย่างมีสติหากคุณต้องการคุณค่าที่แตกต่าง หมายความว่าคุณจะไม่ถูกปรับเปลี่ยนสำเนาที่คิดว่าคุณกำลังแก้ไขต้นฉบับ
Lucas

19
@Lucas ฉันคิดว่าคุณกำลังพูดถึงสำเนาที่แตกต่างกันฉันกำลังพูดถึงสำเนาอัตโนมัติที่ทำขึ้นจากการส่งผ่านค่าของคุณ 'สำเนาที่ทำอย่างมีสติ' ของคุณนั้นแตกต่างกันตามวัตถุประสงค์ที่คุณไม่ได้ทำผิด ไม่ได้คัดลอกมันเป็น instants ใหม่โดยเจตนาที่มีข้อมูลต่าง
จรจัด

3
การแก้ไขของคุณ (16 เดือนต่อมา) ทำให้ชัดเจนยิ่งขึ้น ฉันยังคงสนับสนุน "(struct ไม่เปลี่ยนรูป) หมายความว่าคุณจะไม่ได้รับการแก้ไขการคัดลอกความคิดที่คุณกำลังแก้ไขต้นฉบับ" แม้ว่า
ลูคัส

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

2
วรรค 3 ฟังดูผิดหรือไม่ชัดเจน หากโครงสร้างของคุณไม่เปลี่ยนรูปคุณจะไม่สามารถแก้ไขฟิลด์หรือฟิลด์ของสำเนาใด ๆ ที่ทำ "ถ้าคุณต้องการเปลี่ยนคุณต้อง ... "ที่ทำให้เข้าใจผิดเช่นกันคุณไม่สามารถเปลี่ยนแปลงได้ ตลอดเวลาไม่ว่าจะโดยไม่รู้ตัวหรือไม่รู้ตัว การสร้างอินสแตนซ์ใหม่ซึ่งข้อมูลที่คุณต้องการไม่มีส่วนเกี่ยวข้องกับสำเนาต้นฉบับอื่นนอกจากโครงสร้างข้อมูลเดียวกัน
Saeb Amini

167

จะเริ่มต้นที่ไหน ;-p

บล็อกของ Eric Lippertนั้นดีเสมอสำหรับข้อความ:

นี่เป็นอีกเหตุผลว่าทำไมประเภทค่าที่ไม่แน่นอนเป็นสิ่งชั่วร้าย พยายามทำให้ประเภทของค่าไม่เปลี่ยนรูปเสมอ

ก่อนอื่นคุณมักจะสูญเสียการเปลี่ยนแปลงค่อนข้างง่าย ... ตัวอย่างเช่นนำสิ่งต่าง ๆ ออกจากรายการ:

Foo foo = list[0];
foo.Name = "abc";

สิ่งนั้นเปลี่ยนไปอย่างไร ไม่มีประโยชน์ ...

เช่นเดียวกับคุณสมบัติ:

myObj.SomeProperty.Size = 22; // the compiler spots this one

บังคับให้คุณทำ:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

วิกฤตน้อยกว่ามีปัญหาขนาด; วัตถุที่ไม่แน่นอนมักจะมีคุณสมบัติหลายอย่าง แต่ถ้าคุณมีโครงสร้างที่มีสองints, stringa DateTimeและ a boolคุณสามารถเขียนได้อย่างรวดเร็วผ่านหน่วยความจำมากมาย ด้วยคลาสผู้เรียกหลายคนสามารถแชร์การอ้างอิงไปยังอินสแตนซ์เดียวกัน (การอ้างอิงมีขนาดเล็ก)


5
ใช่แล้ว แต่คอมไพเลอร์ก็โง่เช่นกัน ไม่อนุญาตให้มอบหมายให้สมาชิกทรัพย์สิน struct เป็น IMHO การตัดสินใจการออกแบบโง่เพราะมันจะได้รับอนุญาตให้++ประกอบการ ในกรณีนี้คอมไพเลอร์เพิ่งเขียนการกำหนดอย่างชัดเจนตัวเองแทนที่จะเร่งรีบโปรแกรมเมอร์
Konrad Rudolph

12
@Konrad: myObj.SomeProperty.Size = 22 จะแก้ไขสำเนาของ myObj.SomeProperty คอมไพเลอร์กำลังบันทึกคุณจากจุดบกพร่องที่เห็นได้ชัด และไม่อนุญาตให้ใช้ ++
ลูคัส

@ Lucas: ผู้แปล Mono C # อนุญาตอย่างแน่นอน - เนื่องจากฉันไม่มี Windows ฉันไม่สามารถตรวจสอบคอมไพเลอร์ของ Microsoft ได้
Konrad Rudolph

6
@ Konrad - โดยมีทางอ้อมน้อยกว่าหนึ่งอันควรทำงานได้ มันคือ "การกลายพันธุ์คุณค่าของบางสิ่งที่มีอยู่เป็นเพียงค่าชั่วคราวบนสแต็กและกำลังจะระเหยไปสู่ความว่างเปล่า" ซึ่งเป็นกรณีที่ถูกบล็อก
Marc Gravell

2
@Marc Gravell: ในส่วนของโค้ดก่อนหน้าคุณจะจบลงด้วย "Foo" ซึ่งมีชื่อว่า "abc" และคุณลักษณะอื่น ๆ ของรายการคือ [0] โดยไม่รบกวนรายการ [0] หาก Foo เป็นคลาสคุณจำเป็นต้องโคลนและเปลี่ยนสำเนา ในใจของฉันปัญหาใหญ่ที่มีความแตกต่างของค่ากับคลาสคือการใช้ "." ผู้ประกอบการเพื่อวัตถุประสงค์สองประการ ถ้าฉันมี druthers ของฉันชั้นเรียนสามารถรองรับทั้ง "" และ "->" สำหรับวิธีการและคุณสมบัติ แต่ความหมายปกติสำหรับ "." คุณสมบัติจะเป็นการสร้างอินสแตนซ์ใหม่ด้วยการแก้ไขฟิลด์ที่เหมาะสม
supercat

71

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

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

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


1
Eric Lippert บอกว่าพวกเขาคือ ... ดูคำตอบของฉัน
Marc Gravell

42
มากที่สุดเท่าที่ฉันเคารพ Eric Lippert เขาไม่ใช่พระเจ้า (หรืออย่างน้อยก็ยังไม่ได้) โพสต์บล็อกคุณเชื่อมโยงไปและโพสต์ดังกล่าวเป็นข้อโต้แย้งที่เหมาะสมสำหรับการทำ structs ไม่เปลี่ยนรูปเป็นเรื่องของหลักสูตร แต่พวกเขาเป็นจริงอ่อนแอมากเป็นข้อโต้แย้งที่ไม่เคยใช้ structs ไม่แน่นอน อย่างไรก็ตามโพสต์นี้เป็น +1
สตีเฟ่นมาร์ติ

2
การพัฒนาใน C # คุณมักจะต้องมีความไม่แน่นอนในขณะนี้โดยเฉพาะกับโมเดลธุรกิจของคุณที่คุณต้องการให้สตรีมมิ่ง ฯลฯ ทำงานได้อย่างราบรื่นด้วยโซลูชันที่มีอยู่ ฉันเขียนบทความเกี่ยวกับวิธีการทำงานกับข้อมูลที่ไม่แน่นอนและไม่เปลี่ยนรูปแบบการแก้ไขปัญหาส่วนใหญ่เกี่ยวกับความไม่แน่นอน (ฉันหวังว่า): rickyhelgesson.wordpress.com/2012/07/17/ …
Ricky Helgesson

3
@StephenMartin: Structs ซึ่ง encapsulate ค่าเดียวมักจะไม่เปลี่ยนรูป แต่ structs เป็นสื่อที่ดีที่สุดสำหรับ encapsulating ชุดคงที่ของตัวแปรอิสระ แต่ที่เกี่ยวข้อง (เช่นพิกัด X และ Y ของจุด) ซึ่งไม่มี "identity" เป็น กลุ่ม. Structs ซึ่งจะใช้สำหรับว่าวัตถุประสงค์โดยทั่วไปควรเปิดเผยตัวแปรของพวกเขาเป็นเขตที่สาธารณะ ฉันจะพิจารณาความคิดที่ว่าเหมาะสมที่จะใช้คลาสมากกว่าโครงสร้างเพื่อวัตถุประสงค์ดังกล่าวจะผิดธรรมดา คลาสที่ไม่เปลี่ยนรูปมักจะมีประสิทธิภาพน้อยกว่าและคลาสที่ไม่แน่นอนจะมีความหมายที่น่ากลัว
supercat

2
@StephenMartin: ยกตัวอย่างเช่นเมธอดหรือคุณสมบัติที่ควรจะคืนค่าfloatคอมโพเนนต์ทั้งหกของการแปลงกราฟิก หากวิธีการเช่นนี้ส่งกลับโครงสร้างเขตข้อมูลที่เปิดเผยด้วยส่วนประกอบหกอย่างเห็นได้ชัดว่าการปรับเปลี่ยนเขตข้อมูลของโครงสร้างจะไม่แก้ไขวัตถุกราฟิกที่ได้รับมา หากวิธีการดังกล่าวส่งคืนวัตถุคลาสที่ไม่แน่นอนอาจมีการเปลี่ยนแปลงคุณสมบัติของมันจะเปลี่ยนวัตถุกราฟิกพื้นฐานและอาจจะไม่ - ไม่มีใครรู้จริง ๆ
supercat

48

โครงสร้างที่ไม่แน่นอนไม่ได้เลวร้าย

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

ฉันจะไม่เรียกการใช้โครงสร้างที่ไม่เปลี่ยนรูปในกรณีการใช้ "ความชั่วร้าย" ที่ใช้ได้อย่างสมบูรณ์

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

อย่างไรก็ตามแทนที่จะติดป้ายกำกับที่ไม่เปลี่ยนรูปแบบว่า "ชั่วร้าย" ฉันจะแนะนำให้โอบกอดภาษาและสนับสนุนกฎที่เป็นประโยชน์และสร้างสรรค์มากขึ้น

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


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

41

โครงสร้างที่มีฟิลด์หรือคุณสมบัติที่ไม่แน่นอนสาธารณะไม่ได้เลวร้าย

วิธีการโครงสร้าง (แตกต่างจากตัวตั้งค่าคุณสมบัติ) ซึ่งกลายพันธุ์ "นี่" ค่อนข้างเลวร้ายเพียงเพราะ. net ไม่ได้ให้วิธีการแยกพวกเขาจากวิธีการที่ไม่ วิธีการสร้างที่ไม่ได้กลายพันธุ์ "นี้" ควรจะเรียกใช้แม้ในโครงสร้างแบบอ่านอย่างเดียวโดยไม่จำเป็นต้องคัดลอกเพื่อป้องกัน วิธีการที่ทำให้กลายพันธุ์ "นี้" ไม่ควรเรียกใช้กับโครงสร้างแบบอ่านอย่างเดียว เนื่องจาก. net ไม่ต้องการห้ามวิธีการ struct ที่ไม่แก้ไข "this" จากการเรียกใช้บนแบบอ่านอย่างเดียว แต่ไม่ต้องการให้กลายเป็นแบบอ่านอย่างเดียวกลายพันธุ์มันจึงคัดลอก struct ในแบบอ่านอย่างละเอียด บริบทเท่านั้นเนื้อหาที่ได้รับที่เลวร้ายที่สุดของโลกทั้งสอง

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

struct PointyStruct {public int x, y, z;};
คลาส PointyClass {public int x, y, z;};

ถือเป็นโมฆะ Method1 (PointyStruct foo);
ถือเป็นโมฆะ Method2 (อ้างอิง PointyStruct foo);
ถือเป็นโมฆะ Method3 (PointyClass foo);

สำหรับแต่ละวิธีให้ตอบคำถามต่อไปนี้:

  1. สมมติว่าวิธีการไม่ใช้รหัส "ไม่ปลอดภัย" มันจะแก้ไข foo หรือไม่
  2. หากไม่มีการอ้างอิงภายนอกไปยัง 'foo' ก่อนที่จะมีการเรียกเมธอดการอ้างอิงภายนอกจะมีอยู่หลังจากนั้นหรือไม่?

คำตอบ:

คำถามที่ 1::
Method1()ไม่มี(เจตนาชัดเจน)
Method2() : ใช่(เจตนาชัดเจน)
Method3() : ใช่(เจตนาไม่แน่นอน)
คำถามที่ 2
Method1():: ไม่
Method2(): ไม่ใช่(ยกเว้นไม่ปลอดภัย)
Method3() : ใช่

วิธีที่ 1 ไม่สามารถแก้ไข foo และไม่เคยได้รับการอ้างอิง Method2 ได้รับการอ้างอิงแบบสั้น ๆ เกี่ยวกับ foo ซึ่งสามารถใช้แก้ไขฟิลด์ของ foo ได้หลายครั้งในลำดับใด ๆ จนกว่าจะส่งคืน แต่ก็ไม่สามารถยืนยันการอ้างอิงนั้นได้ ก่อนที่ Method2 จะส่งคืนเว้นแต่ว่าจะใช้รหัสที่ไม่ปลอดภัยสำเนาใด ๆ และทั้งหมดที่อาจทำจากการอ้างอิง 'foo' จะหายไป วิธีที่ 3 ซึ่งแตกต่างจากวิธีที่ 2 ได้รับการอ้างอิงที่คมชัดที่คมชัดเพื่อ foo และไม่มีการบอกว่ามันอาจทำอะไรกับมัน มันอาจไม่เปลี่ยน foo เลยก็อาจเปลี่ยน foo แล้วกลับมาหรืออาจให้การอ้างอิง foo ไปยังเธรดอื่นซึ่งอาจกลายพันธุ์ในบางวิธีโดยพลการในเวลาอนาคตโดยพลการ

อาร์เรย์ของโครงสร้างมีความหมายที่ยอดเยี่ยม กำหนด RectArray [500] ชนิดสี่เหลี่ยมผืนผ้ามันชัดเจนและชัดเจนวิธีการคัดลอกองค์ประกอบ 123 องค์ประกอบ 456 และจากนั้นบางครั้งในภายหลังตั้งความกว้างขององค์ประกอบ 123 ถึง 555 โดยไม่รบกวนองค์ประกอบ 456 "RectArray [432] = RectArray [321 ]; ... ; RectArray [123] .Width = 555; " การรู้ว่า Rectangle เป็น struct ที่มีเขตข้อมูลจำนวนเต็มที่เรียกว่า Width จะบอกสิ่งที่ทุกคนต้องการทราบเกี่ยวกับข้อความข้างต้น

ทีนี้สมมติว่า RectClass เป็นคลาสที่มีเขตข้อมูลเดียวกับสี่เหลี่ยมผืนผ้าและอีกหนึ่งต้องการดำเนินการแบบเดียวกันกับ RectClassArray [500] ประเภท RectClass บางทีอาเรย์ควรจะเก็บไว้ 500 อ้างอิงล่วงหน้าไม่เปลี่ยนรูปไปยังวัตถุ RectClass ไม่แน่นอน ในกรณีนั้นรหัสที่เหมาะสมจะเป็นอะไรเช่น "RectClassArray [321] .BoundBase (RectClassArray [456]); ... ; RectClassArray [321] .X = 555;" บางทีอาร์เรย์อาจถูกสมมติให้เก็บอินสแตนซ์ที่จะไม่เปลี่ยนแปลงดังนั้นรหัสที่เหมาะสมจะเป็นเช่น "RectClassArray [321] = RectClassArray [456]; ... ; RectClassArray [321] = ใหม่ RectClassArray [321] ]); RectClassArray [321] .X = 555; " หากต้องการทราบสิ่งที่ควรทำเราจะต้องรู้มากขึ้นทั้งเกี่ยวกับ RectClass (เช่นรองรับตัวสร้างสำเนา, วิธีคัดลอกจาก ฯลฯ ) ) และการใช้งานที่ตั้งใจของอาร์เรย์ ไม่มีที่ไหนใกล้สะอาดเท่าการใช้ struct

เพื่อให้แน่ใจว่าไม่มีวิธีที่ดีสำหรับคลาสคอนเทนเนอร์ใด ๆ นอกเหนือจากอาเรย์เพื่อให้ความหมายที่ชัดเจนของอาร์เรย์อาเรย์ สิ่งที่ดีที่สุดสามารถทำได้หากต้องการให้มีการจัดทำดัชนีเช่นสตริงอาจจะเสนอวิธีทั่วไป "ActOnItem" ซึ่งจะยอมรับสตริงสำหรับดัชนีพารามิเตอร์ทั่วไปและผู้รับมอบสิทธิ์ที่จะผ่าน โดยอ้างอิงทั้งพารามิเตอร์ทั่วไปและรายการคอลเลกชัน ที่จะช่วยให้เกือบจะมีความหมายเช่นเดียวกับ struct อาเรย์ แต่ถ้า vb.net และ C # คนสามารถติดตามเพื่อเสนอไวยากรณ์ที่ดีรหัสจะเป็น clunky ดูแม้ว่าจะมีประสิทธิภาพที่สมเหตุสมผล (ผ่านพารามิเตอร์ทั่วไปจะ อนุญาตให้ใช้ผู้รับมอบสิทธิ์แบบคงที่และจะหลีกเลี่ยงความต้องการสร้างอินสแตนซ์คลาสชั่วคราวใด ๆ )

โดยส่วนตัวแล้วฉันรู้สึกเกลียดชังที่ Eric Lippert และคณะ spew เกี่ยวกับประเภทค่าที่ไม่แน่นอน พวกเขามีความหมายที่สะอาดกว่าประเภทอ้างอิงที่หลากหลายที่ใช้กันทั่วสถานที่ แม้จะมีข้อ จำกัด บางประการกับการสนับสนุน. net สำหรับประเภทค่ามีหลายกรณีที่ประเภทค่าที่ไม่แน่นอนสามารถปรับให้พอดีได้ดีกว่าเอนทิตีชนิดอื่น


1
@ Ron Warholic: ไม่ปรากฏว่า SomeRect เป็นรูปสี่เหลี่ยมผืนผ้า มันอาจเป็นประเภทอื่น ๆ ซึ่งสามารถ typecast จากสี่เหลี่ยมผืนผ้าโดยปริยาย แม้ว่าประเภทเดียวที่กำหนดโดยระบบซึ่งสามารถพิมพ์ได้โดยปริยายจาก Rectangle คือ RectangleF และคอมไพเลอร์จะเบลอถ้ามีคนพยายามส่งผ่านฟิลด์ของ RectangleF ไปยังตัวสร้างของ Rectangle (ตั้งแต่อดีตเป็นโสดและ Integer หลัง) อาจมีโครงสร้างที่ผู้ใช้กำหนดซึ่งอนุญาตให้ typecast แบบนัยดังกล่าว BTW คำสั่งแรกจะทำงานได้ดีพอ ๆ กันไม่ว่าจะเป็น SomeRect เป็น Rectangle หรือ RectangleF
supercat

สิ่งที่คุณแสดงให้เห็นคือในตัวอย่างที่วางแผนไว้คุณเชื่อว่าวิธีหนึ่งนั้นชัดเจนกว่า หากเรานำตัวอย่างของคุณไปด้วยRectangleฉันสามารถหาคำพูดธรรมดา ๆ ที่คุณมีพฤติกรรมที่ไม่ชัดเจน พิจารณาว่า WinForms ใช้Rectangleประเภทที่ไม่แน่นอนที่ใช้ในBoundsคุณสมบัติของแบบฟอร์ม ถ้าฉันต้องการเปลี่ยนขอบเขตฉันต้องการใช้ไวยากรณ์ที่ดีของคุณ: form.Bounds.X = 10;อย่างไรก็ตามการเปลี่ยนแปลงนี้ไม่มีอะไรแน่นอนในแบบฟอร์ม (และสร้างข้อผิดพลาดที่น่ารักแจ้งให้คุณทราบเช่นนี้) ความไม่สอดคล้องกันคือความหายนะของการเขียนโปรแกรมและเป็นสาเหตุที่ไม่ต้องการความผันแปร
Ron Warholic

3
@Ron Warholic: BTW ผมจะชอบที่จะสามารถที่จะพูดว่า "form.Bounds.X = 10;" และใช้งานได้ แต่ระบบไม่ได้ให้วิธีการที่สะอาด ข้อตกลงสำหรับการเปิดเผยคุณสมบัติประเภทมูลค่าเป็นวิธีการที่รับโทรกลับสามารถเสนอโค้ดที่สะอาดมีประสิทธิภาพและยืนยันได้ถูกต้องมากกว่าวิธีการใด ๆ ที่ใช้คลาส
supercat

4
คำตอบนี้ลึกซึ้งยิ่งกว่าคำตอบที่ได้รับการโหวตสูงสุด มันเป็นเรื่องไร้สาระที่การโต้เถียงกับประเภทค่าที่ไม่แน่นอนนั้นขึ้นอยู่กับแนวคิดของ "สิ่งที่คุณคาดหวัง" ที่จะเกิดขึ้นเมื่อคุณผสมนามแฝงและการกลายพันธุ์ นั่นเป็นสิ่งที่น่ากลัวที่จะทำอย่างใด !
Eamon Nerbonne

2
@supercat: ใครจะรู้บางทีคุณสมบัติ ref-return ที่พวกเขากำลังพูดถึงสำหรับ C # 7 อาจครอบคลุมฐานนั้น (ฉันไม่ได้ดูในรายละเอียดจริง ๆ แต่มันฟังดูเผินๆ)
Eamon Nerbonne

24

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

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

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


พวกเขาควรแสดงแนวคิดที่ไม่เปลี่ยนรูปอย่างน้อย ;-p
Marc Gravell

3
ฉันคิดว่าพวกเขาสามารถทำให้ System.Drawing.Point ไม่เปลี่ยนรูป แต่มันจะเป็นข้อผิดพลาดในการออกแบบที่ร้ายแรง IMHO ฉันคิดว่าคะแนนนั้นเป็นประเภทของคุณค่าตามตัวอักษรและมันไม่แน่นอน และพวกเขาจะไม่ทำให้เกิดปัญหาใด ๆ สำหรับผู้ที่เริ่มต้นเขียนโปรแกรมตั้งแต่เริ่มต้นจริงๆ
สตีเฟ่นมาร์ติ

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

3
Value-types มีประโยชน์สำหรับการแสดงแนวคิดที่ไม่เปลี่ยนรูปแบบได้ง่าย แต่โครงสร้างของฟิลด์ที่เปิดเผยเป็นประเภทที่ดีที่สุดที่จะใช้เพื่อเก็บหรือส่งผ่านชุดค่าคงที่เล็ก ๆ ที่เกี่ยวข้อง แต่เป็นอิสระ (เช่นพิกัดของจุด) ที่เก็บสินค้าของประเภทค่านั้นสรุปคุณค่าของเขตข้อมูลและไม่มีอะไรอื่น ในทางตรงกันข้ามที่เก็บสินค้าของประเภทอ้างอิงที่ไม่แน่นอนอาจถูกนำมาใช้เพื่อวัตถุประสงค์ในการรักษาสถานะของวัตถุที่ไม่แน่นอน แต่ยังห่อหุ้มตัวตนของการอ้างอิงอื่น ๆ ทั้งหมดทั่วจักรวาลที่มีอยู่กับวัตถุเดียวกันนั้น
supercat

4
“ ประเภทค่าโดยทั่วไปแสดงถึงแนวคิดที่ไม่เปลี่ยนรูป” ไม่พวกเขาทำไม่ได้ หนึ่งในแอปพลิเคชันที่เก่าแก่ที่สุดและมีประโยชน์ที่สุดของตัวแปรที่พิมพ์ตามตัวอักษรคือตัวintวนซ้ำซึ่งจะไร้ประโยชน์อย่างสมบูรณ์ถ้ามันไม่เปลี่ยนรูป ฉันคิดว่าคุณกำลังสับสน“ การใช้งานคอมไพเลอร์ / รันไทม์ของประเภทค่า” กับ“ ตัวแปรที่พิมพ์ลงในประเภทของค่า” - อันหลังแน่นอนว่าไม่แน่นอนกับค่าใด ๆ ที่เป็นไปได้
Slipp D. Thompson

19

มีอีกสองสามมุมที่อาจนำไปสู่พฤติกรรมที่ไม่สามารถคาดเดาได้จากมุมมองของโปรแกรมเมอร์

ประเภทค่าที่ไม่เปลี่ยนรูปและฟิลด์แบบอ่านอย่างเดียว

    // Simple mutable structure. 
    // Method IncrementI mutates current state.
    struct Mutable
    {
        public Mutable(int i) : this() 
        {
            I = i;
        }

        public void IncrementI() { I++; }

        public int I { get; private set; }
    }

    // Simple class that contains Mutable structure
    // as readonly field
    class SomeClass 
    {
        public readonly Mutable mutable = new Mutable(5);
    }

    // Simple class that contains Mutable structure
    // as ordinary (non-readonly) field
    class AnotherClass 
    {
        public Mutable mutable = new Mutable(5);
    }

    class Program
    {
        void Main()
        {
            // Case 1. Mutable readonly field
            var someClass = new SomeClass();
            someClass.mutable.IncrementI();
            // still 5, not 6, because SomeClass.mutable field is readonly
            // and compiler creates temporary copy every time when you trying to
            // access this field
            Console.WriteLine(someClass.mutable.I);

            // Case 2. Mutable ordinary field
            var anotherClass = new AnotherClass();
            anotherClass.mutable.IncrementI();

            // Prints 6, because AnotherClass.mutable field is not readonly
            Console.WriteLine(anotherClass.mutable.I);
        }
    }

ชนิดและอาร์เรย์ของค่าที่ไม่แน่นอน

สมมติว่าเรามีอาร์เรย์ของโครงสร้างของเราMutableและเรากำลังเรียกIncrementIวิธีการสำหรับองค์ประกอบแรกของอาร์เรย์นั้น คุณคาดหวังพฤติกรรมอะไรจากการโทรนี้ ควรเปลี่ยนค่าของอาเรย์หรือคัดลอกเท่านั้น?

    Mutable[] arrayOfMutables = new Mutable[1];
    arrayOfMutables[0] = new Mutable(5);

    // Now we actually accessing reference to the first element
    // without making any additional copy
    arrayOfMutables[0].IncrementI();

    // Prints 6!!
    Console.WriteLine(arrayOfMutables[0].I);

    // Every array implements IList<T> interface
    IList<Mutable> listOfMutables = arrayOfMutables;

    // But accessing values through this interface lead
    // to different behavior: IList indexer returns a copy
    // instead of an managed reference
    listOfMutables[0].IncrementI(); // Should change I to 7

    // Nope! we still have 6, because previous line of code
    // mutate a copy instead of a list value
    Console.WriteLine(listOfMutables[0].I);

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


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

2
เหล่านี้เป็นตัวอย่างที่ดีของปัญหาที่ละเอียดอ่อนมากที่จะเกิดขึ้นจาก struct ที่ไม่แน่นอน ฉันจะไม่คาดหวังพฤติกรรมนี้ เหตุใดอาร์เรย์จึงให้การอ้างอิงแก่คุณ แต่อินเทอร์เฟซให้คุณค่ากับคุณ ฉันจะคิดนอกเหนือจากค่า - ตลอดเวลา (ซึ่งเป็นสิ่งที่ฉันคาดหวังจริงๆ) ว่าอย่างน้อยก็น่าจะเป็นวิธีอื่น: อินเตอร์เฟสที่ให้การอ้างอิง อาร์เรย์ให้ค่า ...
Dave Cousineau

@Sahuagin: น่าเสียดายที่ไม่มีกลไกมาตรฐานที่อินเตอร์เฟซสามารถเปิดเผยการอ้างอิงได้ มีหลายวิธีที่. net สามารถอนุญาตให้ทำสิ่งต่าง ๆ ได้อย่างปลอดภัยและเป็นประโยชน์ (เช่นการกำหนดโครงสร้าง "ArrayRef <T>" พิเศษที่ประกอบด้วย a T[]และดัชนีจำนวนเต็มและการเข้าถึงคุณสมบัติประเภทArrayRef<T>จะถูกตีความว่าเป็น การเข้าถึงองค์ประกอบอาเรย์ที่เหมาะสม) [ถ้าคลาสต้องการเปิดเผยArrayRef<T>เพื่อวัตถุประสงค์อื่นใดก็สามารถให้วิธีการ - ซึ่งตรงข้ามกับทรัพย์สิน - เพื่อดึงมัน] น่าเสียดายที่ไม่มีบทบัญญัติดังกล่าวเกิดขึ้น
supercat

2
โอ้ข้า ... นี่ทำให้สิ่งที่ไม่แน่นอนที่เปลี่ยนแปลงไม่ได้!
nawfal

1
ฉันชอบคำตอบนี้เพราะมีข้อมูลที่มีค่ามากซึ่งไม่ชัดเจน แต่จริงๆแล้วนี่ไม่ใช่ข้อโต้แย้งต่อต้าน structable ที่ไม่แน่นอนเหมือนการอ้างสิทธิ์บางอย่าง ใช่สิ่งที่เราเห็นที่นี่คือ "หลุมแห่งความสิ้นหวัง" อย่างที่ Eric คาดไว้ แต่แหล่งที่มาของความสิ้นหวังนี้ไม่ใช่ความผันแปร แหล่งที่มาของความสิ้นหวังเป็น structs ตนเองกรรมวิธีวิธี (สำหรับสาเหตุที่อาร์เรย์และรายการทำงานแตกต่างกันเป็นเพราะโดยทั่วไปแล้วโอเปอเรเตอร์ที่คำนวณที่อยู่หน่วยความจำและอีกอันเป็นคุณสมบัติโดยทั่วไปทุกอย่างจะชัดเจนเมื่อคุณเข้าใจว่า "การอ้างอิง" เป็นค่าที่อยู่)
AnorZaken

18

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

ดังนั้นโครงสร้างที่ไม่แน่นอนไม่ได้เลวร้าย C # ทำให้พวกเขาชั่วร้าย ฉันใช้ structable ที่ไม่แน่นอนใน C ++ ตลอดเวลาและสะดวกและใช้งานง่าย ในทางตรงกันข้าม C # ทำให้ฉันละทิ้ง structs อย่างสมบูรณ์ในฐานะสมาชิกของคลาสเพราะวิธีที่พวกเขาจัดการกับวัตถุ ความสะดวกสบายของพวกเขาทำให้เราเสียค่าใช้จ่าย


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

13

หากคุณยึดติดกับโครงสร้างที่มีไว้สำหรับ (ใน C #, Visual Basic 6, Pascal / Delphi, C ++ struct ประเภท (หรือคลาส) เมื่อพวกเขาไม่ได้ใช้เป็นตัวชี้) คุณจะพบว่าโครงสร้างไม่เกินตัวแปรผสม . ซึ่งหมายความว่า: คุณจะถือว่าพวกเขาเป็นชุดตัวแปรที่บรรจุภายใต้ชื่อสามัญ (ตัวแปรบันทึกที่คุณอ้างอิงสมาชิกจาก)

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

ใช่: โครงสร้างเกี่ยวข้องกับหน่วยความจำจำนวนมาก แต่มันจะไม่เป็นความจำที่แม่นยำมากขึ้นโดยทำ:

point.x = point.x + 1

เปรียบเทียบกับ:

point = Point(point.x + 1, point.y)

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

แต่ในที่สุดโครงสร้างก็เป็นโครงสร้างไม่ใช่วัตถุ ใน POO คุณสมบัติหลักของวัตถุคือตัวตนของพวกเขาซึ่งส่วนใหญ่ไม่เกินที่อยู่หน่วยความจำ โครงสร้างย่อมาจากโครงสร้างข้อมูล (ไม่ใช่วัตถุที่เหมาะสมและเพื่อให้พวกเขาไม่มีตัวตนใด ๆ ) และข้อมูลสามารถแก้ไขได้ ในภาษาอื่น ๆบันทึก (แทนที่จะเป็นstructเช่นในกรณีของ Pascal) เป็นคำศัพท์และมีวัตถุประสงค์เดียวกัน: เพียงแค่ตัวแปรบันทึกข้อมูลตั้งใจที่จะอ่านจากไฟล์แก้ไขและทิ้งลงในไฟล์ (นั่นคือหลัก ใช้และในหลาย ๆ ภาษาคุณสามารถกำหนดการจัดแนวข้อมูลในเรกคอร์ดในขณะที่ไม่จำเป็นสำหรับกรณีที่เรียกว่าออบเจ็กต์อย่างถูกต้อง)

ต้องการเป็นตัวอย่างที่ดี? Structs ใช้สำหรับอ่านไฟล์ได้อย่างง่ายดาย Python มีไลบรารี่นี้เพราะมันเป็น object-oriented และไม่ได้รับการสนับสนุนสำหรับ structs จึงต้องใช้มันในอีกทางหนึ่งซึ่งค่อนข้างน่าเกลียด ภาษาที่ใช้งาน structs มีคุณลักษณะนั้น ... ในตัว ลองอ่านส่วนหัวของบิตแมปที่มีโครงสร้างที่เหมาะสมในภาษาเช่น Pascal หรือ C มันจะง่าย (ถ้าโครงสร้างนั้นถูกสร้างและจัดตำแหน่งอย่างเหมาะสมใน Pascal คุณจะไม่ใช้การเข้าถึงแบบบันทึก แต่ฟังก์ชั่นในการอ่านข้อมูลไบนารี ดังนั้นสำหรับไฟล์และการเข้าถึงหน่วยความจำโดยตรง (ในระบบ) โครงสร้างจะดีกว่าวัตถุ สำหรับวันนี้เราคุ้นเคยกับ JSON และ XML และดังนั้นเราจึงลืมการใช้ไฟล์ไบนารี (และเป็นผลข้างเคียงการใช้ structs) แต่ใช่: มีอยู่และมีจุดประสงค์

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

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


12

ลองนึกภาพคุณมีอาร์เรย์ 1,000,000 อาเรย์ แต่ละ struct เป็นตัวแทนของทุนด้วยสิ่งต่าง ๆ เช่น bid_price, offer_price (อาจเป็นทศนิยม) และอื่น ๆ สิ่งนี้สร้างโดย C # / VB

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

ลองนึกภาพรหัส C # / VB คือฟังฟีดตลาดของการเปลี่ยนแปลงราคารหัสนั้นอาจต้องเข้าถึงองค์ประกอบบางส่วนของอาร์เรย์ (สำหรับความปลอดภัยใดก็ตาม) แล้วปรับเปลี่ยนฟิลด์ราคาบางส่วน

ลองจินตนาการว่าสิ่งนี้กำลังทำอยู่นับหมื่นหรือหลายแสนครั้งต่อวินาที

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

ฮิวโก้


3
จริง แต่ในกรณีนี้เหตุผลในการใช้ struct คือการกระทำนั้นถูกกำหนดโดยการออกแบบแอปพลิเคชันโดยข้อ จำกัด ภายนอก (โดยการใช้งานของรหัสเนทีฟ) ทุกสิ่งที่คุณอธิบายเกี่ยวกับวัตถุเหล่านี้แสดงให้เห็นว่าพวกเขาควรจะเป็นคลาสใน C # หรือ VB.NET อย่างชัดเจน
Jon Hanna

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

9

เมื่อสิ่งที่สามารถกลายพันธุ์ได้ก็จะได้รับความเป็นตัวตน

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

เพราะPersonไม่แน่นอนมันเป็นธรรมชาติมากขึ้นที่จะคิดเกี่ยวกับการเปลี่ยนตำแหน่งของเอริคกว่าโคลนเอริคย้ายโคลนและทำลายเดิม การดำเนินการทั้งสองอย่างจะประสบความสำเร็จในการเปลี่ยนแปลงเนื้อหาของeric.positionแต่การดำเนินการหนึ่งทำได้ง่ายกว่าการดำเนินการอื่น ในทำนองเดียวกันมันง่ายกว่าที่จะส่ง Eric ไปรอบ ๆ (เป็นข้อมูลอ้างอิง) เพื่อหาวิธีในการปรับเปลี่ยนเขา การให้วิธีการโคลนของ Eric มักจะเป็นเรื่องที่น่าประหลาดใจ ใครก็ตามที่ต้องการจะกลายพันธุ์Personต้องจำไว้ว่าให้ขอการอ้างอิงPersonหรือพวกเขาจะทำสิ่งผิด

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

  • ไม่เปลี่ยนรูป
  • ประเภทอ้างอิง
  • ปลอดภัยที่จะผ่านตามตัวอักษร

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

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

ได้รับเนื่องจากการเขียนโปรแกรมการทำงานได้สอนเรามีประโยชน์ที่จะทำให้ทุกอย่างไม่เปลี่ยนรูปแบบ (ไม่มีใครสามารถอ้างอิงericและกลายพันธุ์กับเขาได้อย่างลับๆ) แต่เนื่องจากนั่นไม่ใช่ลักษณะเฉพาะใน OOP จึงยังคงเป็นเรื่องง่ายสำหรับคนอื่น ๆ รหัส.


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

1
ถ้าThingเป็นประเภทคลาสที่ไม่แน่นอน a Thing[] จะห่อหุ้มตัวตนของวัตถุ - ไม่ว่าใครต้องการมันหรือไม่ - เว้นแต่จะมีใครสามารถมั่นใจได้ว่าไม่มีThingในอาเรย์ที่มีการอ้างอิงภายนอกใด ๆ ที่มีอยู่จะกลายพันธุ์ หากไม่มีใครต้องการให้องค์ประกอบของอาร์เรย์ห่อหุ้มตัวตนโดยทั่วไปต้องให้แน่ใจว่าไม่มีรายการใดที่มันมีการอ้างอิงที่จะกลายพันธุ์หรือไม่มีการอ้างอิงภายนอกใด ๆ ที่จะมีอยู่ในรายการใด ๆ ที่มันเก็บไว้ ] วิธีการทั้งสองไม่สะดวกมาก ถ้าThingเป็นโครงสร้างThing[]encapsulate ค่าเท่านั้น
supercat

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

6

มันไม่มีส่วนเกี่ยวข้องกับ structs (และไม่ใช่ C #, อย่างใดอย่างหนึ่ง) แต่ใน Java คุณอาจพบปัญหากับวัตถุที่ไม่แน่นอนเมื่อพวกมันเป็นกุญแจในแผนที่แฮช หากคุณเปลี่ยนพวกเขาหลังจากเพิ่มพวกเขาลงในแผนที่และมันเปลี่ยนรหัสแฮชสิ่งชั่วร้ายอาจเกิดขึ้น


3
นั่นเป็นเรื่องจริงถ้าคุณใช้คลาสเป็นกุญแจสำคัญในแผนที่ด้วย
Marc Gravell

6

ส่วนตัวเมื่อฉันดูรหัสต่อไปนี้ดู clunky สวยให้ฉัน:

data.value.set (data.value.get () + 1);

มากกว่าเพียงแค่

data.value ++; หรือ data.value = data.value + 1;

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

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

สำหรับฉันนี่เป็นการป้องกันการใช้โครงสร้างที่ถูกต้องในการจัดระเบียบตัวแปรสาธารณะถ้าฉันต้องการควบคุมการเข้าถึงฉันจะใช้คลาส


1
ตรงประเด็น! โครงสร้างเป็นหน่วยองค์กรที่ไม่มีข้อ จำกัด การควบคุมการเข้าถึง! น่าเสียดายที่ C # ทำให้พวกเขาไร้ประโยชน์เพื่อจุดประสงค์นี้!
ThunderGr

นี่เป็นจุดที่ขาดไปอย่างสิ้นเชิงเนื่องจากทั้งสองตัวอย่างของคุณแสดงโครงสร้างที่ไม่แน่นอน
vidstige

C # ทำให้พวกเขาไร้ประโยชน์เพื่อจุดประสงค์นี้เพราะนั่นไม่ใช่จุดประสงค์ของโครงสร้าง
Luiz Felipe

6

มีหลายปัญหากับตัวอย่างของ Mr. Eric Lippert มันมีจุดมุ่งหมายเพื่อแสดงให้เห็นถึงจุดที่ structs จะถูกคัดลอกและวิธีการที่อาจเป็นปัญหาหากคุณไม่ระวัง ดูตัวอย่างฉันเห็นว่าเป็นผลมาจากนิสัยการเขียนโปรแกรมที่ไม่ดีและไม่ได้มีปัญหากับ struct หรือคลาสอย่างแท้จริง

  1. โครงสร้างควรจะมีสมาชิกสาธารณะเท่านั้นและไม่ควรมีการห่อหุ้มใด ๆ ถ้าเป็นเช่นนั้นจริง ๆ แล้วควรเป็นประเภท / คลาส คุณไม่จำเป็นต้องสร้างสองสิ่งเพื่อพูดสิ่งเดียวกัน

  2. หากคุณมีคลาสที่ล้อมรอบโครงสร้างคุณจะเรียกวิธีการในชั้นเรียนเพื่อกลายพันธุ์โครงสร้างสมาชิก นี่คือสิ่งที่ฉันจะทำเป็นนิสัยการเขียนโปรแกรมที่ดี

การดำเนินการที่เหมาะสมจะเป็นดังนี้

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

ดูเหมือนว่ามันเป็นปัญหากับนิสัยการเขียนโปรแกรมซึ่งตรงข้ามกับปัญหาของ struct เอง Structs ควรจะไม่แน่นอนนั่นคือความคิดและความตั้งใจ

ผลลัพธ์ของการเปลี่ยนแปลง voila ทำงานตามที่คาดไว้:

1 2 3 กดปุ่มใด ๆ เพื่อดำเนินการต่อ . .


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

5

มีข้อดีและข้อเสียมากมายสำหรับข้อมูลที่ไม่แน่นอน ข้อเสียล้านดอลลาร์คือ aliasing หากมีการใช้ค่าเดียวกันในหลาย ๆ สถานที่และหนึ่งในนั้นมีการเปลี่ยนแปลงค่านั้นจะมีการเปลี่ยนแปลงอย่างน่าอัศจรรย์ไปยังสถานที่อื่น ๆ ที่ใช้มัน เรื่องนี้เกี่ยวข้องกับ แต่ไม่เหมือนกันกับสภาพการแข่งขัน

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

Art of the Interpreterเข้าสู่การแลกเปลี่ยนในรายละเอียดบางอย่างและให้ตัวอย่างบางอย่าง


structs ไม่ได้เป็นนามแฝงใน c # การกำหนดโครงสร้างทุกครั้งเป็นสำเนา
เรียกซ้ำ

@ recursive: ในบางกรณีนั่นเป็นข้อได้เปรียบที่สำคัญของ struct ที่ไม่แน่นอนและสิ่งหนึ่งที่ทำให้ฉันตั้งคำถามกับความคิดที่ว่า structs ไม่ควรเปลี่ยนแปลงได้ ความจริงที่ว่าคอมไพเลอร์บางครั้งโดยปริยายคัดลอก struct ไม่ได้ลดประโยชน์ของ struct ไม่แน่นอน
supercat

1

ฉันไม่เชื่อว่าพวกเขาชั่วถ้าใช้อย่างถูกต้อง ฉันจะไม่ใส่มันในรหัสการผลิตของฉัน แต่ฉันต้องการบางอย่างเช่นหน่วยทดสอบ mocks ที่มีโครงสร้างซึ่งอายุขัยของ struct ค่อนข้างเล็ก

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

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


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

... ซึ่งเห็นได้ชัดว่าไม่มีความหมายเกินกว่า "แต่ละฟิลด์มีสิ่งสุดท้ายที่เขียนถึงมัน" ผลักความหมายทั้งหมดลงในรหัสที่ใช้โครงสร้างมากกว่าที่จะพยายามทำให้โครงสร้างทำได้มากกว่า ได้รับตัวอย่างเช่นRange<T>ประเภทกับสมาชิกMinimumและMaximumด้านของประเภทTและรหัสRange<double> myRange = foo.getRange();การค้ำประกันใด ๆ เกี่ยวกับสิ่งที่MinimumและมีควรมาจากMaximum foo.GetRange();การมีRangeโครงสร้างแบบเปิดเผยจะทำให้ชัดเจนว่าจะไม่เพิ่มพฤติกรรมใด ๆ ของตนเอง
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.