เนื่องจากไม่มีใครให้คำตอบนี้อย่างชัดเจนฉันจะเพิ่มสิ่งต่อไปนี้:
การใช้งานอินเทอร์เฟซบนโครงสร้างไม่มีผลเสียใด ๆ
ตัวแปรใด ๆของประเภทอินเทอร์เฟซที่ใช้ในการเก็บโครงสร้างจะส่งผลให้มีการใช้ค่าแบบบรรจุกล่องของโครงสร้างนั้น หากโครงสร้างไม่เปลี่ยนรูป (เป็นสิ่งที่ดี) นี่เป็นปัญหาด้านประสิทธิภาพที่แย่ที่สุดเว้นแต่คุณจะ:
- การใช้วัตถุที่เป็นผลลัพธ์เพื่อวัตถุประสงค์ในการล็อค (เป็นความคิดที่ไม่ดีอย่างมาก)
- โดยใช้ความหมายความเท่าเทียมกันอ้างอิงและคาดหวังว่ามันจะทำงานสำหรับค่าแบบบรรจุกล่องสองค่าจากโครงสร้างเดียวกัน
ทั้งสองอย่างนี้ไม่น่าจะเป็นไปได้ แต่คุณน่าจะทำอย่างใดอย่างหนึ่งต่อไปนี้:
Generics
เหตุผลที่เหมาะสมหลายคนอาจจะ structs การใช้อินเตอร์เฟซเป็นเพื่อให้พวกเขาสามารถนำมาใช้ภายในทั่วไปบริบทที่มีข้อ จำกัด เมื่อใช้ในรูปแบบนี้ตัวแปรจะเป็นดังนี้:
class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T>
{
private readonly T a;
public bool Equals(Foo<T> other)
{
return this.a.Equals(other.a);
}
}
- เปิดใช้งานการใช้โครงสร้างเป็นพารามิเตอร์ชนิด
- ตราบเท่าที่ไม่มีการใช้ข้อ จำกัด อื่น ๆ เช่น
new()
หรือclass
ใช้
- อนุญาตให้หลีกเลี่ยงการชกมวยในโครงสร้างที่ใช้ในลักษณะนี้
ถ้าอย่างนั้นสิ่งนี้ไม่ใช่การอ้างอิงอินเทอร์เฟซดังนั้นจึงไม่ทำให้เกิดกล่องอะไรก็ตามที่วางไว้ในนั้น ยิ่งไปกว่านั้นเมื่อคอมไพเลอร์ c # รวบรวมคลาสทั่วไปและจำเป็นต้องแทรกการเรียกใช้วิธีการอินสแตนซ์ที่กำหนดไว้ในอินสแตนซ์ของพารามิเตอร์ Type T มันสามารถใช้opcode ที่จำกัด ได้ :
หาก thisType เป็นชนิดค่าและวิธีการนี้ใช้วิธีนี้ ptr จะถูกส่งผ่านโดยไม่มีการปรับเปลี่ยนเป็นตัวชี้ 'this' ไปยังคำสั่งวิธีการเรียกสำหรับการใช้เมธอดโดย thisType
สิ่งนี้จะหลีกเลี่ยงการชกมวยและเนื่องจากประเภทค่าที่ใช้งานอินเทอร์เฟซจะต้องใช้วิธีการนี้จึงจะไม่มีการชกมวยเกิดขึ้น ในตัวอย่างด้านบนการEquals()
เรียกใช้จะทำโดยไม่มีช่องนี้ a 1 .
API แรงเสียดทานต่ำ
structs ส่วนใหญ่ควรจะมีดั้งเดิมเหมือนความหมายที่บิตค่าเหมือนกันจะถือว่าเท่ากับ2 รันไทม์จะให้พฤติกรรมดังกล่าวโดยนัยEquals()
แต่อาจช้า นอกจากนี้ความเท่าเทียมกันโดยปริยายนี้จะไม่ถูกเปิดเผยว่าเป็นการใช้งานIEquatable<T>
และป้องกันไม่ให้มีการใช้โครงสร้างเป็นกุญแจสำหรับพจนานุกรมได้อย่างง่ายดายเว้นแต่จะนำไปใช้อย่างชัดเจนด้วยตนเอง ดังนั้นจึงเป็นเรื่องปกติที่ประเภทโครงสร้างสาธารณะหลายประเภทจะประกาศว่าพวกเขานำไปใช้IEquatable<T>
(โดยที่T
พวกเขาเอง) เพื่อทำให้สิ่งนี้ง่ายขึ้นและทำงานได้ดีขึ้นรวมทั้งสอดคล้องกับพฤติกรรมของประเภทค่าที่มีอยู่จำนวนมากภายใน CLR BCL
พื้นฐานทั้งหมดใน BCL ใช้ขั้นต่ำ:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(และดังนั้นIEquatable
)
หลายคนยังใช้IFormattable
อีกหลายประเภทของค่าที่ระบบกำหนดเช่น DateTime, TimeSpan และ Guid ก็ใช้จำนวนมากหรือทั้งหมดเช่นกัน หากคุณใช้งานประเภทที่ 'มีประโยชน์อย่างกว้างขวาง' ในทำนองเดียวกันเช่นโครงสร้างจำนวนเชิงซ้อนหรือค่าความกว้างที่คงที่แบบข้อความการใช้อินเทอร์เฟซทั่วไปเหล่านี้ (อย่างถูกต้อง) จะทำให้โครงสร้างของคุณมีประโยชน์และใช้งานได้มากขึ้น
การยกเว้น
เห็นได้ชัดว่าหากอินเทอร์เฟซมีนัยอย่างมากถึงความไม่แน่นอน (เช่นICollection
) การใช้งานนั้นเป็นความคิดที่ไม่ดีเนื่องจากอาจหมายความว่าคุณทำให้โครงสร้างไม่แน่นอน (นำไปสู่ข้อผิดพลาดประเภทต่างๆที่อธิบายไว้แล้วว่าการแก้ไขเกิดขึ้นกับค่าแบบบรรจุกล่องแทนที่จะเป็นแบบดั้งเดิม ) หรือคุณทำให้ผู้ใช้สับสนโดยมองข้ามผลของวิธีการเช่นAdd()
หรือการโยนข้อยกเว้น
อินเทอร์เฟซจำนวนมากไม่ได้หมายความถึงความไม่แน่นอน (เช่นIFormattable
) และเป็นวิธีการใช้สำนวนเพื่อแสดงฟังก์ชันการทำงานบางอย่างในรูปแบบที่สอดคล้องกัน บ่อยครั้งที่ผู้ใช้โครงสร้างจะไม่สนใจเกี่ยวกับค่าใช้จ่ายในการชกมวยสำหรับพฤติกรรมดังกล่าว
สรุป
เมื่อทำอย่างสมเหตุสมผลในประเภทค่าที่ไม่เปลี่ยนรูปแบบการใช้งานอินเทอร์เฟซที่มีประโยชน์เป็นความคิดที่ดี
หมายเหตุ:
1: โปรดทราบว่าคอมไพลเลอร์อาจใช้สิ่งนี้เมื่อเรียกใช้เมธอดเสมือนบนตัวแปรซึ่งทราบว่าเป็นชนิดโครงสร้างเฉพาะ แต่จำเป็นต้องเรียกใช้เมธอดเสมือน ตัวอย่างเช่น:
List<int> l = new List<int>();
foreach(var x in l)
;
ตัวแจงนับที่ส่งคืนโดยรายการเป็นโครงสร้างการเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยงการจัดสรรเมื่อระบุรายการ (พร้อมผลลัพธ์ที่น่าสนใจบางอย่าง) อย่างไรก็ตามความหมายของ foreach ระบุว่าหาก enumerator ดำเนินการIDisposable
แล้วDispose()
จะถูกเรียกเมื่อการทำซ้ำเสร็จสิ้น เห็นได้ชัดว่าการที่สิ่งนี้เกิดขึ้นผ่านการโทรแบบบรรจุกล่องจะกำจัดผลประโยชน์ใด ๆ ของตัวแจงนับที่เป็นโครงสร้าง (อันที่จริงมันจะแย่กว่านี้) ที่แย่กว่านั้นคือถ้าการโทรออกได้ปรับเปลี่ยนสถานะของตัวแจงนับไม่ทางใดก็ทางหนึ่งสิ่งนี้จะเกิดขึ้นในอินสแตนซ์แบบบรรจุกล่องและอาจมีการใช้ข้อบกพร่องที่ละเอียดอ่อนมากมายในกรณีที่ซับซ้อน ดังนั้น IL ที่ปล่อยออกมาในสถานการณ์เช่นนี้คือ:
IL_0001: newobj System.Collections.Generic.List..ctor
IL_0006: stloc.0
IL_0007: nop
IL_0008: ldloc.0
IL_0009: callvirt System.Collections.Generic.List.GetEnumerator
IL_000E: stloc 2
IL_000F: br.s IL_0019
IL_0011: ldloca.s 02
IL_0013: เรียก System.Collections.Generic.List.get_Current
IL_0018: stloc 1
IL_0019: ldloca.s 02
IL_001B: เรียก System.Collections.Generic.List.MoveNext
IL_0020: stloc 3
IL_0021: ldloc 3
IL_0022: brtrue.s IL_0011
IL_0024: ลา. s IL_0035
IL_0026: ldloca.s 02
IL_0028: ถูก จำกัด System.Collections.Generic.List.Enumerator
IL_002E: callvirt System.IDisposable.Dispose
IL_0033: nop
IL_0034: สิ้นสุดในที่สุด
ดังนั้นการนำ IDisposable ไปใช้ไม่ก่อให้เกิดปัญหาด้านประสิทธิภาพใด ๆ และแง่มุมที่ไม่แน่นอน (น่าเสียใจ) ของตัวแจงนับจะถูกเก็บรักษาไว้หากเมธอด Dispose ทำอะไรได้จริง!
2: double และ float เป็นข้อยกเว้นของกฎนี้โดยที่ค่า NaN ไม่ถือว่าเท่ากัน