คุณจะใช้ struct เมื่อใดแทนคลาส? [ปิด]


174

อะไรคือกฎของหัวแม่มือสำหรับเมื่อคุณใช้ structs กับคลาส? ฉันกำลังคิดถึงนิยาม C # ของคำศัพท์เหล่านั้น แต่หากภาษาของคุณมีแนวคิดที่คล้ายกันฉันต้องการฟังความคิดเห็นของคุณเช่นกัน

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


4
ซ้ำซ้อนที่เป็นไปได้: stackoverflow.com/questions/6267957/struct-vs-class
FrustratedWithFormsDesigner

7
@Frustrated ไม่สามารถทำเครื่องหมายคำถามว่าซ้ำกับบางสิ่งในเว็บไซต์อื่นแม้ว่าจะเกี่ยวข้องกันอย่างแน่นอน
อดัมเลียร์

@ แอนนาเลียร์: ฉันรู้ว่าฉันโหวตให้โยกย้ายไม่ให้ใกล้เคียงกัน เมื่อมีการอพยพก็สามารถปิดเป็นซ้ำได้อย่างถูกต้อง
FrustratedWithFormsDesigner

5
@ เฟร็ดฉันไม่คิดว่าจะต้องย้ายข้อมูล
อดัมเลียร์

15
ดูเหมือนว่าคำถามจะเหมาะกับ P.SE กับ SO มากขึ้น นี่คือเหตุผลที่ฉันถามที่นี่
RationalGeek

คำตอบ:


169

กฎทั่วไปที่จะต้องปฏิบัติตามคือ structs ควรเป็นชุดเล็ก ๆ ที่เรียบง่าย (ระดับเดียว) ของคุณสมบัติที่เกี่ยวข้องซึ่งไม่เปลี่ยนรูปเมื่อสร้างขึ้น สำหรับสิ่งอื่นใช้คลาส

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

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

ตัวอย่างเช่นสมมติว่าคุณมีคลาส SimpleClass พร้อมด้วยคุณสมบัติสองประการคือ A และ B คุณยกตัวอย่างสำเนาของคลาสนี้เริ่มต้น A และ B แล้วส่งอินสแตนซ์ไปยังวิธีอื่น วิธีการนั้นจะปรับเปลี่ยน A และ B เพิ่มเติมในฟังก์ชันการเรียกใช้ (วิธีที่สร้างอินสแตนซ์) A และ B ของอินสแตนซ์ของคุณจะมีค่าที่กำหนดโดยวิธีการที่เรียกใช้

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

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

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

สำหรับตัวอย่างที่ดีให้ดูที่ประเภท DateTime คุณไม่สามารถกำหนดฟิลด์ใด ๆ ของอินสแตนซ์ DateTime ได้โดยตรง คุณต้องสร้างใหม่หรือเรียกใช้วิธีการที่มีอยู่ซึ่งจะสร้างอินสแตนซ์ใหม่ นี่เป็นเพราะวันที่และเวลาเป็น "ค่า" เช่นหมายเลข 5 และการเปลี่ยนหมายเลข 5 ส่งผลให้มีค่าใหม่ที่ไม่ใช่ 5 เพียงเพราะ 5 + 1 = 6 ไม่ได้หมายความว่า 5 คือ 6 เพราะคุณเพิ่ม 1 ลงไป DateTimes ทำงานในลักษณะเดียวกัน 12:00 ไม่ "กลายเป็น" 12:01 หากคุณเพิ่มนาทีคุณจะได้รับค่าใหม่ 12:01 ที่แตกต่างจากเวลา 12:00 หากนี่เป็นสถานการณ์แบบลอจิคัลสำหรับประเภทของคุณ (ตัวอย่างแนวคิดที่ดีที่ไม่ได้สร้างไว้ใน. NET คือเงิน, ระยะทาง, น้ำหนักและปริมาณอื่น ๆ ของ UOM ที่การดำเนินการจะต้องคำนึงถึงค่าทั้งหมดในบัญชี) จากนั้นใช้โครงสร้างและออกแบบตามนั้น ในกรณีอื่น ๆ ส่วนใหญ่ที่รายการย่อยของวัตถุควรไม่แน่นอนอิสระให้ใช้คลาส


1
คำตอบที่ดี! ฉันจะชี้ไปที่วิธีการและการจอง 'Domain Driven Development' ซึ่งดูเหมือนจะค่อนข้างเกี่ยวข้องกับความคิดเห็นของคุณเกี่ยวกับสาเหตุที่เราควรใช้คลาสหรือโครงสร้างตามแนวคิดที่คุณพยายามนำไปใช้จริง ๆ
Maxim Popravko

1
รายละเอียดเล็ก ๆ น้อย ๆ ที่ควรกล่าวถึง: คุณไม่สามารถกำหนดนวกรรมิกเริ่มต้นของคุณเองใน struct ได้ คุณต้องทำกับสิ่งที่เริ่มต้นทุกอย่างเป็นค่าเริ่มต้น โดยปกติแล้วมันค่อนข้างเล็กโดยเฉพาะในชั้นเรียนที่มีคุณสมบัติเหมาะสม แต่จะหมายความว่าการเปลี่ยนคลาสเป็นโครงสร้างอาจมีความหมายมากกว่าการเปลี่ยนคำหลัก
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy - True มี gotchas อื่น ๆ เช่นกัน; structs ไม่สามารถมีลำดับชั้นการสืบทอดได้ พวกเขาสามารถใช้อินเทอร์เฟซ แต่ไม่สามารถสืบทอดมาจากสิ่งอื่นใดนอกจาก System.ValueType
KeithS

ในฐานะที่เป็นจาวาสคริปต์ฉันต้องถามสองสิ่ง ตัวอักษรของวัตถุที่แตกต่างจากประเพณีทั่วไปของโครงสร้างและทำไมมันเป็นสิ่งสำคัญที่ structs จะไม่เปลี่ยนรูป?
Erik Reppen

1
@ErikReppen: เพื่อตอบคำถามทั้งสองข้อของคุณ: เมื่อ struct ถูกส่งเข้าไปในฟังก์ชั่นมันจะถูกส่งโดยค่า ด้วยคลาสคุณกำลังผ่านการอ้างอิงไปยังคลาส (ตามค่า) เอริค Lippert กล่าวถึงสาเหตุปัญหานี้เป็นปัญหา: blogs.msdn.com/b/ericlippert/archive/2008/05/14/... ย่อหน้าสุดท้ายของ KeithS ครอบคลุมคำถามเหล่านี้ด้วย
Brian

14

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

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
อย่างแน่นอน ฉันจะลงคะแนนถ้าฉันทำได้ ฉันไม่เข้าใจว่าแนวคิดมาจากโครงสร้างนั้นสำหรับข้อมูลและวัตถุใด ๆ struct ใน C # เป็นเพียงกลไกสำหรับการจัดสรรบนสแต็ก สำเนาของโครงสร้างทั้งหมดจะถูกส่งผ่านไปรอบ ๆ แทนที่จะเป็นเพียงสำเนาของตัวชี้วัตถุ
mike30

2
@mike - C # structs ไม่จำเป็นต้องจัดสรรบนสแต็ก
ลี

1
@ ไมค์ความคิด "structs สำหรับข้อมูล" อาจมาจากนิสัยที่ได้มาจากการเขียนโปรแกรม C เป็นเวลาหลายปี C ไม่มีคลาสและ structs ของมันเป็นคอนเทนเนอร์ข้อมูลเท่านั้น
Konamiman

เห็นได้ชัดว่ามีโปรแกรมเมอร์ C อย่างน้อย 3 คน (ลุกขึ้นยืน) หลังจากอ่านคำตอบของ Michael K ;-)
bytedev

10

ความแตกต่างที่สำคัญที่สุดระหว่าง a classและ a structคือสิ่งที่เกิดขึ้นในสถานการณ์ต่อไปนี้:

  สิ่งสิ่งที่ 1 = สิ่งใหม่ ();
  thing1.somePropertyOrField = 5;
  สิ่งที่สิ่งที่ 2 = สิ่งที่ 1;
  thing2.somePropertyOrField = 9;

สิ่งที่ควรเป็นผลกระทบของคำสั่งสุดท้ายเมื่อthing1.somePropertyOrField? หากThingstruct และsomePropertyOrFieldเป็นสัมผัสข้อมูลสาธารณะวัตถุthing1และthing2จะเป็น "เดี่ยว" จากแต่ละอื่น ๆ thing1เพื่อให้คำสั่งหลังจะไม่ส่งผลกระทบ หากThingเป็นชั้นแล้วthing1และthing2จะแนบไปกับแต่ละอื่น ๆ thing1.somePropertyOrFieldและเพื่อให้คำสั่งหลังจะเขียน หนึ่งควรใช้โครงสร้างในกรณีที่ความหมายในอดีตจะทำให้รู้สึกมากขึ้นและควรใช้ชั้นเรียนในกรณีที่ความหมายหลังจะทำให้รู้สึกมากขึ้น

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

ตัวอย่างเช่นพิจารณาข้อความ:

  คน somePerson = myPeople.GetPerson ("123-45-6789");
  somePerson.Name = "Mary Johnson"; // เป็น "Mary Smith"

ข้อความที่สองจะเปลี่ยนแปลงข้อมูลที่เก็บไว้myPeopleหรือไม่? ถ้าPersonเป็นโครงสร้างเขตข้อมูลที่เปิดเผยมันจะไม่และความจริงที่ว่ามันจะไม่เป็นผลที่ชัดเจนของการเป็นโครงสร้างเขตข้อมูลที่เปิดเผย ; ถ้าPersonเป็น struct และต้องการจะอัปเดตmyPeopleเราจะต้องทำอะไรบางอย่างอย่างmyPeople.UpdatePerson("123-45-6789", somePerson)ชัดเจน หากPersonเป็นคลาสอย่างไรก็ตามอาจเป็นการยากกว่าที่จะระบุว่ารหัสข้างต้นจะไม่อัปเดตเนื้อหาของMyPeopleอัปเดตทุกครั้งหรือบางครั้งอัปเดต

จากความเห็นเกี่ยวกับความคิดที่ว่าโครงสร้างควรจะ "ไม่เปลี่ยนรูป" ฉันไม่เห็นด้วยโดยทั่วไป มีกรณีการใช้งานที่ถูกต้องสำหรับโครงสร้าง "ที่ไม่เปลี่ยนรูป" (ซึ่งมีการบังคับใช้ค่าคงที่ในตัวสร้าง) แต่กำหนดให้โครงสร้างทั้งหมดต้องถูกเขียนใหม่ทุกครั้งที่ส่วนใดส่วนหนึ่งของการเปลี่ยนแปลงนั้นเป็นที่น่าอึดอัดใจสิ้นเปลืองและมีแนวโน้มมากขึ้น เขตข้อมูลโดยตรง ยกตัวอย่างเช่นพิจารณาPhoneNumberstruct ที่มีสาขารวมถึงหมู่คนอื่น ๆAreaCodeและและคิดว่าคนมีExchange List<PhoneNumber>ผลกระทบของสิ่งต่อไปนี้ควรชัดเจน:

  สำหรับ (int i = 0; i <myList.Count; i ++)
  {
    หมายเลขโทรศัพท์ theNumber = myList [i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue (theNumber.Exchange), ออก newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList [i] = theNumber;
      }
    }
  }

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

BTW มีอย่างน้อยสองกรณีที่ structs อาจเก็บการอ้างอิงถึงชนิดที่ไม่แน่นอน

  1. ซีแมนทิกส์ของโครงสร้างระบุว่ามันเก็บข้อมูลประจำตัวของวัตถุที่เป็นปัญหาแทนที่จะเป็นทางลัดสำหรับเก็บคุณสมบัติของวัตถุ ตัวอย่างเช่น "KeyValuePair" จะเก็บเอกลักษณ์ของปุ่มบางปุ่ม แต่คาดว่าจะไม่เก็บข้อมูลถาวรเกี่ยวกับตำแหน่งของปุ่มเหล่านั้นสถานะไฮไลต์ ฯลฯ
  2. โครงสร้างรู้ว่ามันเก็บการอ้างอิงเพียงอย่างเดียวกับวัตถุนั้นไม่มีใครจะได้รับการอ้างอิงและการกลายพันธุ์ใด ๆ ที่จะถูกดำเนินการกับวัตถุนั้นจะได้รับการดำเนินการก่อนที่จะมีการอ้างอิงไปยังที่ใดก็ได้

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


7

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

แหล่งข้อมูลเพิ่มเติม

สำหรับคำอธิบายที่เป็นทางการมากขึ้น MSDN กล่าวว่า: http://msdn.microsoft.com/en-us/library/ms229017.aspx ลิงก์นี้จะตรวจสอบสิ่งที่ฉันกล่าวถึงข้างต้น structs ควรใช้เพื่อสร้างข้อมูลจำนวนเล็กน้อยเท่านั้น


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

2
@ แอนนาเลียร์ขอบคุณที่ให้ฉันรู้ว่าจะเก็บไว้ในใจต่อจากนี้ไป!
rrazd

2
-1 "ฉันมักจะใช้ structs เฉพาะเมื่อมีวิธีหนึ่งที่ต้องการดังนั้นการทำให้คลาสดูเหมือน" หนัก "เกินไป ... หนักหรือ มันแปลว่าอะไร? ... และคุณกำลังบอกว่าจริงๆเพราะมีวิธีหนึ่งแล้วมันน่าจะเป็นโครงสร้างหรือไม่?
bytedev

2

ใช้โครงสร้างสำหรับการสร้างข้อมูลที่บริสุทธิ์และคลาสสำหรับวัตถุที่มีการดำเนินการ

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

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

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


2
"ใช้โครงสร้างสำหรับการสร้างข้อมูลที่บริสุทธิ์และคลาสสำหรับวัตถุที่มีการทำงาน" ใน C # นี่เป็นเรื่องไร้สาระ ดูคำตอบของฉันด้านล่าง
bytedev

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