ยาชื่อสามัญมีการใช้งานอย่างไร?


16

นี่คือคำถามจากมุมมองภายในของคอมไพเลอร์

ฉันสนใจ generics ไม่ใช่แม่แบบ (C ++) ดังนั้นฉันจึงทำเครื่องหมายคำถามด้วย C # ไม่ใช่ Java เพราะ AFAIK ข้อมูลทั่วไปในทั้งสองภาษานั้นแตกต่างกันในการนำไปใช้งาน

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

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

สมมติว่าฉันเห็นสาย:

var x = new Foo<Bar>();

ฉันจะเพิ่มFoo_Barคลาสใหม่ให้กับลำดับชั้นหรือไม่


อัปเดต:จนถึงตอนนี้ฉันพบเพียง 2 โพสต์ที่เกี่ยวข้อง แต่ถึงแม้ว่าพวกเขาจะไม่ได้ลงรายละเอียดมากในแง่ "วิธีการทำด้วยตัวเอง":


Upvoting เพราะฉันคิดว่าคำตอบที่สมบูรณ์จะน่าสนใจ ฉันมีความคิดบางอย่างเกี่ยวกับวิธีการทำงาน แต่ไม่เพียงพอที่จะตอบอย่างถูกต้อง ฉันไม่คิดว่า generics ใน C # รวบรวมคลาสเฉพาะสำหรับแต่ละประเภททั่วไป ดูเหมือนว่าพวกเขาจะได้รับการแก้ไขที่รันไทม์ (อาจมีความเร็วที่เห็นได้ชัดเจนจากการใช้ข้อมูลทั่วไป) บางทีเราสามารถทำให้ Eric Lippert พูดสอดเข้าไปได้?
KChaloux

2
@KChaloux: ที่ระดับ MSIL มีหนึ่งคำอธิบายทั่วไป เมื่อ JIT ทำงานจะสร้างรหัสเครื่องแยกต่างหากสำหรับแต่ละประเภทค่าที่ใช้เป็นพารามิเตอร์ทั่วไปและอีกชุดรหัสเครื่องหนึ่งที่ครอบคลุมประเภทการอ้างอิงทั้งหมด การรักษาคำอธิบายทั่วไปใน MSIL นั้นดีมากเพราะช่วยให้คุณสามารถสร้างอินสแตนซ์ใหม่ที่รันไทม์
Ben Voigt

@Ben นั่นเป็นสาเหตุที่ฉันไม่ได้พยายามตอบคำถามจริง ๆ : p
KChaloux

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

@Telastyn สำหรับหัวข้อเหล่านั้นแน่ใจว่าฉันกำลัง :-) ฉันกำลังมองหาบางสิ่งบางอย่างมันใกล้กับ C # ในกรณีของฉันฉันกำลังรวบรวมเพื่อ PHP (ไม่ตลก) ฉันจะขอบคุณถ้าคุณแบ่งปันความรู้ของคุณ
greenoldman

คำตอบ:


4

วิธีตรวจสอบให้แน่ใจว่าฟิลด์คงที่นั้นเป็นเอกพจน์ต่ออินสแตนซ์ (เช่นแต่ละครั้งที่พารามิเตอร์ทั่วไปได้รับการแก้ไข)

การสร้างอินสแตนซ์ทั่วไปแต่ละรายการจะมีสำเนาของตนเอง (ชื่อที่สับสน) ระเบียบวิธีซึ่งเป็นตำแหน่งที่เก็บฟิลด์สแตติก

สมมติว่าฉันเห็นสาย:

var x = new Foo<Bar>();

ฉันจะเพิ่มFoo_Barคลาสใหม่ให้กับลำดับชั้นหรือไม่

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

แต่ถ้าคุณพิจารณา MethodTables แต่ละตัวมีตัวชี้ทางอ้อมไปยังคลาสพื้นฐานเพื่อจัดลำดับชั้นนี้ใช่แล้วจะเพิ่มคลาสใหม่ให้กับลำดับชั้น


ขอบคุณนั่นเป็นสิ่งที่น่าสนใจ ดังนั้นเขตข้อมูลคงที่จะถูกแก้ไขคล้ายกับตารางเสมือนจริงใช่ไหม มีการอ้างอิงถึงพจนานุกรม "ทั่วโลก" ซึ่งถือรายการต่อแต่ละประเภท? ดังนั้นผมจึงอาจมี 2 ประกอบไม่ทราบว่าของแต่ละอื่น ๆ ที่ใช้และพวกเขาจะไม่ผลิตสองกรณีของสนามคงที่จากFoo<string> Foo
greenoldman

1
@greenoldman ดีไม่เหมือนกับโต๊ะเสมือนอย่างแน่นอน MethodTable มีทั้งสแตติกฟิลด์และการอ้างอิงถึงเมธอดของชนิดที่ใช้ในการแจกจ่ายเสมือน (นั่นคือเหตุผลที่เรียกว่า MethodTable) และใช่ CLR ต้องมีตารางบางอย่างที่สามารถใช้ในการเข้าถึง MethodTables ทั้งหมด
svick

2

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

ฟิลด์สแตติกจะให้อินสแตนซ์แยกต่างหากสำหรับแต่ละอินสแตนซ์ทั่วไปได้อย่างไร

สำหรับสมาชิกแบบสแตติกซึ่งไม่เกี่ยวข้องกับพารามิเตอร์ประเภททั่วไปมันค่อนข้างง่าย (ใช้พจนานุกรมที่แมปจากพารามิเตอร์ทั่วไปกับค่า)

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

แต่ละอินสแตนซ์ทั่วไปปรากฏแยกต่างหากในลำดับชั้นชนิดหรือไม่

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

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


ปัญหาของฉันคือฉันไม่สามารถหลีกเลี่ยงการคิดในแง่ของเทมเพลต ยกตัวอย่างเช่น - แตกต่างแม่แบบคลาทั่วไปที่มีการรวบรวมอย่างเต็มที่ ซึ่งหมายความว่าในการชุมนุมอื่นโดยใช้คลาสนี้จะเกิดอะไรขึ้น วิธีการคอมไพล์แล้วเรียกว่ากับการคัดเลือกนักแสดงภายใน? ฉันสงสัยว่ายาชื่อสามัญสามารถพึ่งพาข้อ จำกัด - แทนที่จะเป็นข้อโต้แย้งมิฉะนั้นFoo<int>และFoo<string>จะตีข้อมูลเดียวกันกับFooข้อ จำกัด โดยไม่มี
greenoldman

1
@greenoldman: เราสามารถหลีกเลี่ยงประเภทค่าเป็นเวลาหนึ่งนาทีได้เพราะพวกเขาได้รับการจัดการเป็นพิเศษหรือไม่ ถ้าคุณได้List<string>และList<Form>แล้วตั้งแต่List<T>ภายในมีสมาชิกคนหนึ่งของชนิดT[]และมีข้อ จำกัด ไม่มีTแล้วสิ่งที่คุณจะได้รับจริงเป็นรหัสเครื่องที่ปรุงแต่ง object[]อย่างไรก็ตามเนื่องจากมีเพียงTอินสแตนซ์เท่านั้นที่ใส่ลงในอาร์เรย์จึงสามารถส่งคืนทุกสิ่งที่ออกมาTโดยไม่ต้องมีการตรวจสอบประเภทเพิ่มเติม ในทางตรงกันข้ามถ้าคุณมีControlCollection<T> where T : Controlแล้วอาร์เรย์ภายในก็จะกลายเป็นT[] Control[]
Ben Voigt

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

3
@greenoldman: Java ทำการลบประเภทในขั้นตอนการแปลซอร์ส -> bytecode ซึ่งทำให้เป็นไปไม่ได้ที่ผู้ตรวจสอบยืนยันรหัสทั่วไป C # ทำในขั้นตอน bytecode-> รหัสเครื่อง
Ben Voigt

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

1

แต่จะทำอย่างไรกับชั้นเรียนทั่วไปและที่สำคัญกว่านั้นจัดการกับการอ้างอิงได้อย่างไร

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

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

ส่วนท้ายฉันไม่รู้จริงๆ งานทั้งหมดของฉันกับยาชื่อสามัญได้ตั้งเป้าหมาย CIL เป็นแบ็กเอนด์ดังนั้นฉันจึงสามารถรวบรวมพวกมันให้เป็นยาชื่อสามัญที่รองรับได้ที่นั่น


ขอบคุณมาก (น่าเสียดายที่ฉันไม่สามารถตอบได้หลายคำตอบ) เป็นเรื่องที่ดีมากที่ได้ยินว่าฉันได้ทำขั้นตอนนั้นค่อนข้างถูกต้อง - ในกรณีของฉันList<T>มีประเภทของจริง (คำจำกัดความ) ในขณะที่List<Foo>(ขอบคุณสำหรับคำศัพท์เช่นกัน) ด้วยวิธีการของฉันถือเป็นการประกาศของList<T>(แน่นอนตอนนี้ผูกพันกับFooแทนT)
greenoldman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.