แหล่งที่มาที่อ้างอิงโดย OP มีความน่าเชื่อถือบางอย่าง ... แต่สิ่งที่เกี่ยวกับ Microsoft - ท่าทางในการใช้งาน struct คืออะไร? ฉันค้นหาการเรียนรู้เพิ่มเติมจาก Microsoftและนี่คือสิ่งที่ฉันพบ:
พิจารณาการกำหนดโครงสร้างแทนคลาสถ้าอินสแตนซ์ของชนิดมีขนาดเล็กและอายุสั้นทั่วไปหรือถูกฝังอยู่ในวัตถุอื่น
อย่ากำหนดโครงสร้างเว้นแต่ประเภทจะมีคุณสมบัติดังต่อไปนี้ทั้งหมด:
- มันมีเหตุผลแสดงค่าเดียวคล้ายกับประเภทดั้งเดิม (จำนวนเต็มคู่และอื่น ๆ )
- มันมีขนาดอินสแตนซ์ที่มีขนาดเล็กกว่า 16 ไบต์
- มันไม่เปลี่ยนรูป
- มันจะไม่ต้องมีกล่องบ่อย
Microsoft ละเมิดกฎเหล่านั้นอย่างสม่ำเสมอ
ตกลง # 2 และ # 3 ต่อไป พจนานุกรมอันเป็นที่รักของเรามีโครงสร้างภายใน 2 แบบ:
[StructLayout(LayoutKind.Sequential)] // default for structs
private struct Entry //<Tkey, TValue>
{
// View code at *Reference Source
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator :
IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable,
IDictionaryEnumerator, IEnumerator
{
// View code at *Reference Source
}
* แหล่งอ้างอิง
แหล่งที่มา 'JonnyCantCode.com' ได้รับ 3 จาก 4 - ค่อนข้างลืมได้ตั้งแต่ # 4 อาจจะไม่เป็นปัญหา หากคุณพบว่าตัวเองต่อสู้โครงสร้างให้คิดใหม่สถาปัตยกรรมของคุณ
ลองดูว่าทำไม Microsoft จะใช้ structs เหล่านี้:
- แต่ละโครงสร้าง
Entry
และEnumerator
แสดงถึงค่าเดียว
- ความเร็ว
Entry
ไม่เคยผ่านเป็นพารามิเตอร์นอกคลาสพจนานุกรม การตรวจสอบเพิ่มเติมแสดงให้เห็นว่าเพื่อให้เป็นไปตามการนำ IEnumerable มาใช้พจนานุกรมใช้โครงสร้างEnumerator
ที่คัดลอกทุกครั้งที่มีการร้องขอ enumerator ... เหมาะสม
- ภายในสู่คลาสพจนานุกรม
Enumerator
เป็นสาธารณะเนื่องจากพจนานุกรมนับได้และต้องมีความสามารถในการเข้าถึงอินเทอร์เฟซ IEnumerator ที่เท่าเทียมกันเช่น IEnumerator getter
อัปเดต - นอกจากนี้ให้ตระหนักว่าเมื่อโครงสร้างใช้อินเทอร์เฟซตามที่ตัวแจงนับทำและถูกส่งไปยังชนิดที่นำไปใช้งานนั้นโครงสร้างจะกลายเป็นประเภทอ้างอิงและย้ายไปที่กอง ภายในชั้นพจนานุกรมแจงนับเป็นยังคงเป็นประเภทค่า อย่างไรก็ตามทันทีที่มีการเรียกเมธอดGetEnumerator()
การอ้างอิงชนิดIEnumerator
จะถูกส่งคืน
สิ่งที่เราไม่เห็นในที่นี้คือความพยายามหรือข้อพิสูจน์ความต้องการเพื่อให้ structs ไม่เปลี่ยนรูปหรือรักษาขนาดอินสแตนซ์ขนาดเพียง 16 ไบต์หรือน้อยกว่า:
- ไม่มีการประกาศอะไรในโครงสร้างด้านบน
readonly
- ไม่เปลี่ยนรูป
- ขนาดของโครงสร้างเหล่านี้อาจมีขนาดเกิน 16 ไบต์
Entry
มีเวลาชั่วชีวิตบึกบึน (จากAdd()
ไปRemove()
, Clear()
หรือเก็บขยะ);
และ ... 4. โครงสร้างทั้งสองจัดเก็บ TKey และ TValue ซึ่งเราทุกคนรู้ว่ามีความสามารถในการเป็นประเภทอ้างอิง (เพิ่มข้อมูลโบนัส)
คีย์ที่แฮชแม้จะมีพจนานุกรมบางส่วนก็รวดเร็วเนื่องจากการวางโครงสร้างไว้นั้นเร็วกว่าประเภทอ้างอิง ที่นี่ฉันมีDictionary<int, int>
ที่เก็บจำนวนเต็ม 300,000 สุ่มด้วยปุ่มเพิ่มขึ้นตามลำดับ
ความจุ: 312874
MemSize: 2660827 bytes
เสร็จสมบูรณ์ปรับขนาด: 5ms
เวลาทั้งหมดที่จะเติม: 889ms
ความจุ : จำนวนขององค์ประกอบที่มีอยู่ก่อนที่จะต้องปรับขนาดอาร์เรย์ภายใน
MemSize : กำหนดโดยการทำให้เป็นอันดับพจนานุกรมใน MemoryStream และรับความยาวไบต์ (ถูกต้องเพียงพอสำหรับวัตถุประสงค์ของเรา)
เสร็จสิ้นการปรับขนาด : เวลาที่ใช้ในการปรับขนาดอาร์เรย์ภายในจากองค์ประกอบ 150,862 เป็น 312874 องค์ประกอบ เมื่อคุณคิดว่าแต่ละองค์ประกอบถูกคัดลอกไปตามลำดับArray.CopyTo()
นั่นจะไม่โทรมเกินไป
เวลาทั้งหมดในการกรอก : เบ้ยอมรับเนื่องจากการบันทึกและOnResize
เหตุการณ์ที่ฉันเพิ่มลงในแหล่งที่มา อย่างไรก็ตามยังคงน่าประทับใจในการเติมจำนวนเต็ม 300k ในขณะที่ปรับขนาด 15 ครั้งในระหว่างการดำเนินการ แค่อยากรู้อยากเห็นอะไรที่จะเติมเต็มถ้าฉันรู้ความสามารถแล้ว? 13ms
ดังนั้นตอนนี้ถ้าEntry
เป็นคลาสล่ะ เวลาหรือตัวชี้วัดเหล่านี้จะแตกต่างกันมาก
ความจุ: 312874
MemSize: 2660827 bytes
เสร็จสมบูรณ์ปรับขนาด: 26ms
เวลาทั้งหมดที่จะเติม: 964ms
เห็นได้ชัดว่าความแตกต่างใหญ่คือในการปรับขนาด ความแตกต่างใด ๆ หากพจนานุกรมถูกกำหนดค่าเริ่มต้นด้วยความจุ ไม่พอที่จะเกี่ยวข้องกับ ... 12ms
สิ่งที่เกิดขึ้นคือเนื่องจากEntry
เป็นโครงสร้างจึงไม่จำเป็นต้องมีการเตรียมใช้งานเหมือนประเภทอ้างอิง นี่คือทั้งความงามและความหายนะของประเภทค่า เพื่อที่จะใช้Entry
เป็นประเภทการอ้างอิงฉันต้องใส่รหัสต่อไปนี้:
/*
* Added to satisfy initialization of entry elements --
* this is where the extra time is spent resizing the Entry array
* **/
for (int i = 0 ; i < prime ; i++)
{
destinationArray[i] = new Entry( );
}
/* *********************************************** */
เหตุผลที่ฉันต้องเริ่มต้นแต่ละองค์ประกอบของอาร์เรย์Entry
เป็นชนิดอ้างอิงสามารถพบได้ที่MSDN: การออกแบบโครงสร้าง ในระยะสั้น:
ไม่ต้องจัดให้มีการสร้างเริ่มต้นสำหรับโครงสร้าง
หากโครงสร้างกำหนดตัวสร้างเริ่มต้นเมื่ออาร์เรย์ของโครงสร้างถูกสร้างขึ้นรันไทม์ภาษาทั่วไปจะเรียกใช้ตัวสร้างเริ่มต้นโดยอัตโนมัติในแต่ละองค์ประกอบอาร์เรย์
คอมไพเลอร์บางตัวเช่นคอมไพเลอร์ C # ไม่อนุญาตให้โครงสร้างมีตัวสร้างเริ่มต้น
จริงๆแล้วมันค่อนข้างง่ายและเราจะยืมจากThree Laws of Robotics ของ Asimov :
- โครงสร้างต้องปลอดภัยที่จะใช้
- โครงสร้างต้องปฏิบัติหน้าที่ได้อย่างมีประสิทธิภาพเว้นแต่จะเป็นการละเมิดกฎ # 1
- โครงสร้างต้องยังคงไม่บุบสลายระหว่างการใช้งานเว้นแต่จะต้องทำลายมันเพื่อให้เป็นไปตามกฎ # 1
... เรานำสิ่งใดไปจากสิ่งนี้กล่าวโดยย่อคือรับผิดชอบการใช้ค่าประเภท มันมีความรวดเร็วและมีประสิทธิภาพ แต่มีความสามารถในการทำให้เกิดพฤติกรรมที่ไม่คาดคิดมากมายหากไม่ได้รับการบำรุงรักษาอย่างเหมาะสม (เช่นสำเนาที่ไม่ได้ตั้งใจ)
System.Drawing.Rectangle
ละเมิดกฎทั้งสามข้อนี้