เป้าหมายของการจัดองค์ประกอบที่ชัดเจนพ่ายแพ้โดยการแบ่งปันข้อมูลประเภทมากเกินไประหว่างไลบรารีหรือไม่ บางทีคุณอาจต้องการพื้นที่จัดเก็บข้อมูลที่มีการพิมพ์อย่างมีประสิทธิภาพ แต่จะมีราคาแพงมากหากคุณต้องการอัปเดตสคีมาฐานข้อมูลของคุณทุกครั้งที่โมเดลออบเจ็กต์มีการพัฒนาต้นทุน คุณต้องการอนุมานประเภทของสคีมา ณ รันไทม์หรือไม่ คุณจำเป็นต้องส่งมอบส่วนประกอบที่ยอมรับอ็อบเจ็กต์ผู้ใช้โดยพลการและจัดการพวกมันด้วยวิธีที่ชาญฉลาดหรือไม่ คุณต้องการให้คอมไพเลอร์ของไลบรารีสามารถบอกคุณโดยทางโปรแกรมได้หรือไม่
หากคุณพบว่าตัวเองกำลังดิ้นรน เพื่อรักษาโครงสร้างข้อมูลที่พิมพ์อย่างเข้มงวดในขณะที่เพิ่มความยืดหยุ่นรันไทม์ให้สูงสุด คุณอาจต้องการพิจารณาการสะท้อนกลับและวิธีที่จะสามารถปรับปรุงซอฟต์แวร์ของคุณได้ ในคอลัมน์นี้ ฉันจะสำรวจเนมสเปซ System.Reflection ใน Microsoft .NET Framework และจะเป็นประโยชน์ต่อประสบการณ์การพัฒนาของคุณอย่างไร ฉันจะเริ่มต้นด้วยตัวอย่างง่ายๆ และปิดท้ายด้วยวิธีจัดการกับสถานการณ์การทำให้เป็นอนุกรมในโลกแห่งความเป็นจริง ในระหว่างนี้ ฉันจะแสดงให้เห็นว่า การสะท้อนกลับและ CodeDom ทำงานร่วมกันอย่างไรเพื่อจัดการข้อมูลรันไทม์อย่างมีประสิทธิภาพ
ก่อนที่ฉันจะเจาะลึกเกี่ยวกับ System.Reflection ฉันอยากจะหารือเกี่ยวกับการเขียนโปรแกรมแบบสะท้อนแสงโดยทั่วไป ประการแรก การสะท้อนกลับสามารถกำหนดให้เป็นฟังก์ชันใดๆ ที่ได้รับจากระบบการเขียนโปรแกรมที่ช่วยให้โปรแกรมเมอร์สามารถตรวจสอบและจัดการโค้ดเอนทิตีโดยไม่ต้องมีความรู้มาก่อนเกี่ยวกับตัวตนหรือโครงสร้างที่เป็นทางการ มีส่วนต่างๆ มากมายที่จะกล่าวถึงในส่วนนี้ ดังนั้นฉันจะอธิบายทีละเรื่อง
อันดับแรก การสะท้อนกลับให้อะไรกับคุณบ้าง ฉันมักจะแบ่งงานที่เน้นการสะท้อนกลับออกเป็นสองประเภท: การตรวจสอบและการจัดการ การตรวจสอบจำเป็นต้องมีการวิเคราะห์ออบเจ็กต์และประเภทเพื่อรวบรวมข้อมูลที่มีโครงสร้างเกี่ยวกับคำจำกัดความและพฤติกรรม นอกเหนือจากข้อกำหนดพื้นฐานบางประการแล้ว การดำเนินการนี้มักกระทำโดยปราศจากความรู้ล่วงหน้า (ตัวอย่างเช่น ใน .NET Framework ทุกอย่างสืบทอดมาจาก System.Object และการอ้างอิงถึงประเภทอ็อบเจ็กต์มักจะเป็นจุดเริ่มต้นทั่วไปสำหรับการสะท้อน)
การดำเนินการเรียกใช้โค้ดแบบไดนามิกโดยใช้ข้อมูลที่รวบรวมผ่านการตรวจสอบ การสร้างอินสแตนซ์ใหม่ หรือแม้แต่ ประเภทและวัตถุสามารถปรับโครงสร้างใหม่แบบไดนามิกได้อย่างง่ายดาย จุดสำคัญประการหนึ่งที่ต้องทำคือสำหรับระบบส่วนใหญ่ การจัดการกับประเภทและอ็อบเจ็กต์ที่รันไทม์ส่งผลให้ประสิทธิภาพลดลงเมื่อเทียบกับการดำเนินการที่เทียบเท่ากันในซอร์สโค้ดแบบคงที่ นี่เป็นการแลกเปลี่ยนที่จำเป็นเนื่องจากธรรมชาติของการสะท้อนแบบไดนามิก แต่มีเคล็ดลับและแนวทางปฏิบัติที่ดีที่สุดมากมายในการปรับประสิทธิภาพการสะท้อนให้เหมาะสม (ดู msdn.microsoft.com/msdnmag/issues/05 สำหรับข้อมูลเชิงลึกเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพ การใช้การสะท้อน /07/การสะท้อน)
ดังนั้นเป้าหมายของการไตร่ตรองคืออะไร โปรแกรมเมอร์ตรวจสอบและจัดการอะไรจริงๆ ในคำจำกัดความของการสะท้อนของฉัน ฉันใช้คำศัพท์ใหม่ "เอนทิตีโค้ด" เพื่อเน้นความจริงที่ว่าจากมุมมองของโปรแกรมเมอร์ เทคนิคการสะท้อนบางครั้งทำให้เส้นแบ่งระหว่าง วัตถุและประเภทดั้งเดิม ตัวอย่างเช่น งานทั่วไปที่เน้นการสะท้อนกลับอาจเป็น:
เริ่มต้นด้วยตัวจัดการเพื่อวัตถุ O และใช้การสะท้อนกลับเพื่อรับตัวจัดการสำหรับคำจำกัดความที่เกี่ยวข้อง (ประเภท T)
ตรวจสอบประเภท T และรับหมายเลขอ้างอิงสำหรับวิธี M
เรียกเมธอด M ของอ็อบเจ็กต์อื่น O' (รวมถึงประเภท T)
โปรดทราบว่าฉันกำลังเปลี่ยนจากอินสแตนซ์หนึ่งไปยังประเภทที่ซ่อนอยู่ จากประเภทนั้นไปยังวิธีการ จากนั้นใช้ตัวจัดการของวิธีการเพื่อเรียกมันบนอินสแตนซ์อื่น - แน่นอนว่านี่เป็นการใช้การเขียนโปรแกรม C# แบบดั้งเดิมในซอร์สโค้ดที่เทคโนโลยีไม่สามารถทำได้ หลังจากหารือเกี่ยวกับ System.Reflection ของ .NET Framework ด้านล่างแล้ว ฉันจะอธิบายสถานการณ์นี้อีกครั้งด้วยตัวอย่างที่เป็นรูปธรรม
ภาษาการเขียนโปรแกรมบางภาษามีการสะท้อนโดยธรรมชาติผ่านไวยากรณ์ ในขณะที่แพลตฟอร์มและเฟรมเวิร์กอื่นๆ (เช่น .NET Framework) จัดให้มีเป็นไลบรารีระบบ ไม่ว่าจะให้การสะท้อนอย่างไร ความเป็นไปได้ในการใช้เทคโนโลยีการสะท้อนในสถานการณ์ที่กำหนดนั้นค่อนข้างซับซ้อน ความสามารถของระบบการเขียนโปรแกรมในการสะท้อนกลับขึ้นอยู่กับหลายปัจจัย: โปรแกรมเมอร์ใช้ประโยชน์จากคุณลักษณะของภาษาการเขียนโปรแกรมเพื่อแสดงแนวคิดของเขาหรือไม่ คอมไพเลอร์ฝังข้อมูลที่มีโครงสร้างเพียงพอ (เมตาดาต้า) ในเอาต์พุตเพื่ออำนวยความสะดวกในการวิเคราะห์ในอนาคตหรือไม่? การตีความ มีระบบย่อยรันไทม์หรือโฮสต์ล่ามที่ย่อยข้อมูลเมตานี้หรือไม่ ไลบรารีแพลตฟอร์มนำเสนอผลลัพธ์ของการตีความนี้ในลักษณะที่เป็นประโยชน์ต่อโปรแกรมเมอร์หรือ
ไม่ หากคุณคำนึงถึงระบบประเภทเชิงวัตถุที่ซับซ้อน ปรากฏเป็นฟังก์ชันสไตล์ C ที่เรียบง่ายในโค้ด และไม่มีโครงสร้างข้อมูลที่เป็นทางการ ดังนั้นจึงเป็นไปไม่ได้เลยที่โปรแกรมของคุณจะอนุมานแบบไดนามิกว่าตัวชี้ของตัวแปรบางตัว v1 ชี้ไปที่อินสแตนซ์อ็อบเจ็กต์ประเภท T บางประเภท . เพราะท้ายที่สุดแล้ว ประเภท T นั้นเป็นแนวคิดในหัวของคุณ มันไม่เคยปรากฏอย่างชัดเจนในคำสั่งการเขียนโปรแกรมของคุณ แต่ถ้าคุณใช้ภาษาเชิงวัตถุที่ยืดหยุ่นกว่า (เช่น C#) เพื่อแสดงโครงสร้างนามธรรมของโปรแกรม และแนะนำแนวคิดของประเภท T โดยตรง คอมไพเลอร์จะแปลงความคิดของคุณเป็นสิ่งที่สามารถส่งผ่านในภายหลังได้ ลอจิกที่เหมาะสมเพื่อทำความเข้าใจแบบฟอร์มตามที่กำหนดโดยรันไทม์ภาษาทั่วไป (CLR) หรือล่ามภาษาไดนามิกบางตัว
การสะท้อนกลับเป็นเทคโนโลยีรันไทม์แบบไดนามิกหรือไม่ มีหลายครั้งตลอดวงจรการพัฒนาและการดำเนินการที่การสะท้อนกลับพร้อมใช้งานและเป็นประโยชน์ต่อนักพัฒนา ภาษาโปรแกรมบางภาษาถูกนำมาใช้ผ่านคอมไพเลอร์แบบสแตนด์อโลนที่แปลงโค้ดระดับสูงเป็นคำสั่งโดยตรงที่เครื่องสามารถเข้าใจได้ ไฟล์เอาต์พุตจะมีเฉพาะอินพุตที่คอมไพล์แล้ว และรันไทม์ไม่มีตรรกะที่รองรับสำหรับการยอมรับออบเจ็กต์ทึบแสงและวิเคราะห์คำจำกัดความแบบไดนามิก นี่เป็นกรณีของคอมไพเลอร์ C แบบดั้งเดิมหลายตัว เนื่องจากมีตรรกะสนับสนุนเพียงเล็กน้อยในการเรียกทำงานเป้าหมาย คุณจึงไม่สามารถทำการสะท้อนแบบไดนามิกได้มากนัก แต่คอมไพเลอร์จะให้การสะท้อนแบบคงที่เป็นครั้งคราว ตัวอย่างเช่น ตัวดำเนินการ typeof ที่แพร่หลายช่วยให้โปรแกรมเมอร์ตรวจสอบตัวระบุประเภทในเวลาคอมไพล์
สถานการณ์ที่แตกต่างไปจากเดิมอย่างสิ้นเชิงคือภาษาโปรแกรมที่ตีความจะได้รับการดำเนินการผ่านกระบวนการหลักเสมอ (ภาษาสคริปต์มักจะจัดอยู่ในหมวดหมู่นี้) เนื่องจากคำจำกัดความที่สมบูรณ์ของโปรแกรมพร้อมใช้งาน (เป็นซอร์สโค้ดอินพุต) รวมกับการใช้ภาษาที่สมบูรณ์ (ในฐานะล่ามเอง) เทคนิคทั้งหมดที่จำเป็นในการสนับสนุนการวิเคราะห์ตนเองจึงถูกนำมาใช้ ภาษาไดนามิกนี้มักจะให้ความสามารถในการสะท้อนกลับที่ครอบคลุม เช่นเดียวกับชุดเครื่องมือที่หลากหลายสำหรับการวิเคราะห์แบบไดนามิกและการจัดการโปรแกรม
.NET Framework CLR และภาษาโฮสต์ เช่น C# อยู่ตรงกลาง คอมไพเลอร์ใช้ในการแปลงซอร์สโค้ดเป็น IL และข้อมูลเมตา ส่วนหลังนั้นมี "ตรรกะ" น้อยกว่าซอร์สโค้ด แต่ยังคงรักษาโครงสร้างนามธรรมและข้อมูลประเภทไว้มากมาย เมื่อ CLR เริ่มต้นและโฮสต์โปรแกรมนี้ ไลบรารี System.Reflection ของไลบรารีคลาสพื้นฐาน (BCL) สามารถใช้ข้อมูลนี้และส่งกลับข้อมูลเกี่ยวกับประเภทของออบเจ็กต์ สมาชิกประเภท ลายเซ็นของสมาชิก และอื่นๆ นอกจากนี้ยังสามารถรองรับการโทรได้รวมถึงการโทรสายล่าช้า
การสะท้อนกลับใน .NET
เพื่อใช้ประโยชน์จากการสะท้อนกลับเมื่อเขียนโปรแกรมด้วย .NET Framework คุณสามารถใช้เนมสเปซ System.Reflection เนมสเปซนี้มีคลาสที่ห่อหุ้มแนวคิดรันไทม์มากมาย เช่น แอสเซมบลี โมดูล ประเภท วิธีการ ตัวสร้าง ฟิลด์ และคุณสมบัติ ตารางในรูปที่ 1 แสดงให้เห็นว่าคลาสใน System.Reflection แมปกับรันไทม์เชิงแนวคิดอย่างไร
แม้ว่าจะมีความสำคัญ แต่ System.Reflection.Assembly และ System.Reflection.Module จะใช้เพื่อค้นหาและโหลดโค้ดใหม่ลงในรันไทม์เป็นหลัก ในคอลัมน์นี้ ฉันจะไม่พูดถึงส่วนเหล่านี้และถือว่าโค้ดที่เกี่ยวข้องทั้งหมดได้ถูกโหลดไปแล้ว
ในการตรวจสอบและจัดการโค้ดที่โหลด รูปแบบทั่วไปส่วนใหญ่จะเป็น System.Type โดยทั่วไป คุณจะเริ่มต้นด้วยการรับอินสแตนซ์ System.Type ของคลาสรันไทม์ที่สนใจ (ผ่าน Object.GetType) จากนั้นคุณสามารถใช้วิธีการต่างๆ ของ System.Type เพื่อสำรวจคำจำกัดความของประเภทใน System.Reflection และรับอินสแตนซ์ของคลาสอื่นๆ ตัวอย่างเช่น หากคุณสนใจวิธีการเฉพาะและต้องการรับอินสแตนซ์ System.Reflection.MethodInfo ของวิธีนี้ (อาจผ่าน Type.GetMethod) ในทำนองเดียวกัน หากคุณสนใจฟิลด์ใดฟิลด์หนึ่งและต้องการรับอินสแตนซ์ System.Reflection.FieldInfo ของฟิลด์นี้ (อาจผ่าน Type.GetField)
เมื่อคุณมีออบเจ็กต์อินสแตนซ์การสะท้อนที่จำเป็นทั้งหมดแล้ว คุณสามารถดำเนินการต่อโดยทำตามขั้นตอนสำหรับการตรวจสอบหรือการจัดการตามความจำเป็น เมื่อตรวจสอบ คุณใช้คุณสมบัติเชิงอธิบายต่างๆ ในคลาสสะท้อนเพื่อรับข้อมูลที่คุณต้องการ (นี่เป็นประเภททั่วไปหรือไม่ นี่เป็นวิธีอินสแตนซ์หรือไม่) เมื่อดำเนินการ คุณสามารถเรียกและดำเนินการเมธอดแบบไดนามิก สร้างอ็อบเจ็กต์ใหม่โดยการเรียก Constructor และอื่นๆ
การตรวจสอบประเภทและสมาชิก
มาดูโค้ดบางส่วนและสำรวจวิธีการตรวจสอบโดยใช้การสะท้อนพื้นฐานกันดีกว่า ฉันจะมุ่งเน้นไปที่การวิเคราะห์ประเภท เริ่มต้นด้วยออบเจ็กต์ ฉันจะดึงข้อมูลประเภทของมันแล้วตรวจสอบสมาชิกที่น่าสนใจสองสามรายการ (ดูรูปที่ 2)
สิ่งแรกที่ควรทราบคือในคำจำกัดความของคลาส เมื่อมองแวบแรกดูเหมือนว่ามีพื้นที่ในการอธิบายวิธีการมากกว่าที่ฉันคาดไว้มาก วิธีการพิเศษเหล่านี้มาจากไหน ใครก็ตามที่มีประสบการณ์ในลำดับชั้นของวัตถุ .NET Framework จะรับรู้ถึงวิธีการเหล่านี้ที่สืบทอดมาจากวัตถุคลาสพื้นฐานทั่วไปนั่นเอง (อันที่จริง ฉันใช้ Object.GetType เพื่อดึงข้อมูลประเภทของมันเป็นครั้งแรก) นอกจากนี้ คุณยังสามารถดูฟังก์ชัน getter สำหรับคุณสมบัติได้ แล้วถ้าคุณต้องการเพียงฟังก์ชันที่กำหนดไว้อย่างชัดเจนของ MyClass เอง คุณจะซ่อนฟังก์ชันที่สืบทอดมาได้อย่างไร หรือบางทีคุณอาจต้องการเพียงฟังก์ชันอินสแตนซ์ที่กำหนดไว้อย่างชัดเจน
เพียงแค่ดูออนไลน์ที่ MSDN แล้วคุณจะรู้ ฉันพบว่าทุกคนเต็มใจที่จะใช้วิธีที่สองที่โอเวอร์โหลดของ GetMethods ซึ่งยอมรับพารามิเตอร์ BindingFlags ด้วยการรวมค่าที่แตกต่างจากการแจงนับ BindingFlags คุณสามารถให้ฟังก์ชันส่งคืนเฉพาะชุดย่อยของวิธีการที่ต้องการได้ แทนที่การเรียก GetMethods ด้วย:
GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |BindingFlags.Public)
ด้วยเหตุนี้ คุณจะได้รับผลลัพธ์ต่อไปนี้ (โปรดทราบว่าไม่มีฟังก์ชันตัวช่วยแบบคงที่และฟังก์ชันที่สืบทอดมาจาก System.Object)
ตัวอย่างการสาธิตการสะท้อน 1
ชื่อประเภท:
ชื่อวิธี MyClass:
ชื่อวิธี MyMethod1: ชื่อวิธี MyMethod2
: get_MyProperty
ชื่อคุณสมบัติ: MyProperty
จะเกิดอะไรขึ้นหากคุณทราบชื่อประเภท (ผ่านการรับรองทั้งหมด) และสมาชิกล่วงหน้า คุณจะดึงข้อมูลจากประเภท enum เป็นประเภทได้อย่างไร การแปลงหรือไม่ ด้วยโค้ดในสองตัวอย่างแรก คุณมีองค์ประกอบพื้นฐานในการใช้เบราว์เซอร์คลาสดั้งเดิมอยู่แล้ว คุณสามารถค้นหาเอนทิตีรันไทม์ตามชื่อ จากนั้นระบุคุณสมบัติที่เกี่ยวข้องต่างๆ
การเรียกโค้ดแบบไดนามิก
จนถึงตอนนี้ฉันได้รับการจัดการกับรันไทม์ออบเจ็กต์ (เช่น ประเภทและวิธีการ) เพื่อวัตถุประสงค์ในการอธิบายเท่านั้น เช่น การพิมพ์ชื่อ แต่คุณจะทำอย่างไรเพิ่มเติม จริง ๆ แล้วคุณจะเรียกเมธอดได้อย่างไร
ประเด็นสำคัญบางประการในตัวอย่างนี้คือ ขั้นแรก อินสแตนซ์ System.Type จะถูกดึงมาจากอินสแตนซ์ของ MyClass, mc1 จากนั้น อินสแตนซ์ MethodInfo จะถูกดึงมาจาก ประเภทนั้น สุดท้าย เมื่อเรียกใช้ MethodInfo ก็จะถูกผูกไว้กับอินสแตนซ์ MyClass (mc2) อื่นโดยส่งผ่านเป็นพารามิเตอร์แรกของการโทร
ดังที่ได้กล่าวไว้ก่อนหน้านี้ ตัวอย่างนี้ทำให้ความแตกต่างระหว่างประเภทและการใช้ออบเจ็กต์ไม่ชัดเจนที่คุณคาดว่าจะเห็นในซอร์สโค้ด ตามตรรกะ คุณดึงข้อมูลหมายเลขอ้างอิงไปยังเมธอดแล้ว เรียกเมธอดราวกับว่ามันเป็นของวัตถุอื่น สำหรับโปรแกรมเมอร์ที่คุ้นเคยกับภาษาการเขียนโปรแกรมเชิงฟังก์ชัน อาจเป็นเรื่องง่าย แต่สำหรับโปรแกรมเมอร์ที่คุ้นเคยกับภาษา C# เท่านั้น การแยกการใช้งานวัตถุและการสร้างอินสแตนซ์ของวัตถุอาจไม่ใช่เรื่องง่ายนัก
รวบรวมทั้งหมดเข้าด้วยกัน
จนถึงตอนนี้ ฉันได้พูดถึงหลักการพื้นฐานของการตรวจสอบและการโทรแล้ว และตอนนี้ฉันจะรวบรวมมันพร้อมตัวอย่างที่เป็นรูปธรรม ลองจินตนาการว่าคุณต้องการส่งมอบไลบรารีที่มีฟังก์ชันตัวช่วยแบบคงที่ซึ่งต้องจัดการกับอ็อบเจ็กต์ แต่ ณ เวลาออกแบบ คุณไม่มีความคิดเกี่ยวกับประเภทของวัตถุเหล่านี้! ขึ้นอยู่กับคำแนะนำของผู้เรียกใช้ฟังก์ชันว่าเขาต้องการดึงข้อมูลที่มีความหมายจากวัตถุเหล่านี้อย่างไร ฟังก์ชั่นจะยอมรับคอลเลกชันของวัตถุและคำอธิบายสตริงของวิธีการ จากนั้นจะวนซ้ำผ่านคอลเลกชั่น เรียกเมธอดของอ็อบเจ็กต์แต่ละอัน และรวมค่าที่ส่งคืนด้วยฟังก์ชันบางอย่าง
สำหรับตัวอย่างนี้ ฉันจะประกาศข้อจำกัดบางประการ ขั้นแรก วิธีที่อธิบายโดยพารามิเตอร์สตริง (ซึ่งต้องใช้งานโดยประเภทพื้นฐานของแต่ละออบเจ็กต์) จะไม่ยอมรับพารามิเตอร์ใดๆ และจะส่งกลับจำนวนเต็ม โค้ดจะวนซ้ำผ่านคอลเลกชันของออบเจ็กต์ เรียกวิธีการที่ระบุ และค่อยๆ คำนวณค่าเฉลี่ยของค่าทั้งหมด สุดท้ายนี้ เนื่องจากนี่ไม่ใช่โค้ดที่ใช้งานจริง ฉันจึงไม่ต้องกังวลกับการตรวจสอบความถูกต้องของพารามิเตอร์หรือจำนวนเต็มมากเกินไปเมื่อทำการรวม
เมื่อเรียกดูโค้ดตัวอย่าง คุณจะเห็นว่าข้อตกลงระหว่างฟังก์ชันหลักและตัวช่วยแบบคงที่ ComputeAverage ไม่ต้องอาศัยข้อมูลประเภทใด ๆ นอกเหนือจากคลาสฐานทั่วไปของออบเจ็กต์เอง กล่าวอีกนัยหนึ่ง คุณสามารถเปลี่ยนประเภทและโครงสร้างของออบเจ็กต์ที่กำลังถ่ายโอนได้อย่างสมบูรณ์ แต่ตราบใดที่คุณสามารถใช้สตริงเพื่ออธิบายวิธีการที่ส่งคืนจำนวนเต็มได้ ComputeAverage ก็จะทำงานได้ดี
! ถูกซ่อนอยู่ในตัวอย่างสุดท้ายเกี่ยวข้องกับ MethodInfo (การสะท้อนทั่วไป) โปรดทราบว่าในการวนซ้ำ foreach ของ ComputeAverage โค้ดจะดึง MethodInfo จากออบเจ็กต์แรกในคอลเลกชันเท่านั้น จากนั้นจึงผูกเข้ากับการเรียกออบเจ็กต์ที่ตามมาทั้งหมด ตามที่แสดงการเข้ารหัส มันทำงานได้ดี - นี่คือตัวอย่างง่ายๆ ของการแคช MethodInfo แต่มีข้อ จำกัด พื้นฐานอยู่ที่นี่ อินสแตนซ์ MethodInfo สามารถเรียกได้โดยอินสแตนซ์ประเภทลำดับชั้นเดียวกันกับออบเจ็กต์ที่ดึงข้อมูลเท่านั้น สิ่งนี้เป็นไปได้เนื่องจากมีการส่งผ่านอินสแตนซ์ของ IntReturner และ SonOfIntReturner (สืบทอดมาจาก IntReturner)
ในโค้ดตัวอย่าง มีการรวมคลาสชื่อ EnemyOfIntReturner ไว้ด้วย ซึ่งใช้โปรโตคอลพื้นฐานเดียวกันกับอีกสองคลาสอื่น แต่ไม่มีประเภทที่ใช้ร่วมกันร่วมกัน กล่าวอีกนัยหนึ่ง อินเทอร์เฟซมีความเท่าเทียมกันทางตรรกะ แต่ไม่มีการทับซ้อนกันในระดับประเภท หากต้องการสำรวจการใช้ MethodInfo ในสถานการณ์นี้ ให้ลองเพิ่มอ็อบเจ็กต์อื่นในคอลเลกชัน รับอินสแตนซ์ผ่าน "new EnemyOfIntReturner(10)" และรันตัวอย่างอีกครั้ง คุณจะพบข้อยกเว้นที่ระบุว่าไม่สามารถใช้ MethodInfo เพื่อเรียกอ็อบเจ็กต์ที่ระบุได้ เนื่องจากไม่เกี่ยวข้องกับประเภทดั้งเดิมที่ได้รับ MethodInfo มาก่อน (แม้ว่าชื่อเมธอดและโปรโตคอลพื้นฐานจะเทียบเท่ากันก็ตาม) เพื่อเตรียมการผลิตโค้ดให้พร้อม คุณจะต้องเตรียมพร้อมรับมือกับสถานการณ์นี้
วิธีแก้ปัญหาที่เป็นไปได้คือการวิเคราะห์ประเภทของออบเจ็กต์ที่เข้ามาทั้งหมดด้วยตัวเอง โดยคงการตีความลำดับชั้นของประเภทที่ใช้ร่วมกัน (ถ้ามี) หากประเภทของออบเจ็กต์ถัดไปแตกต่างจากลำดับชั้นของประเภทที่รู้จัก จะต้องรับและจัดเก็บ MethodInfo ใหม่ อีกวิธีหนึ่งคือจับ TargetException และรับอินสแตนซ์ MethodInfo อีกครั้ง โซลูชันทั้งสองที่กล่าวถึงในที่นี้มีข้อดีและข้อเสีย Joel Pobar เขียนบทความที่ยอดเยี่ยมสำหรับนิตยสารฉบับนี้ฉบับเดือนพฤษภาคม 2550 เกี่ยวกับประสิทธิภาพการบัฟเฟอร์และการสะท้อนของ MethodInfo ซึ่งฉันขอแนะนำเป็นอย่างยิ่ง
หวังว่าตัวอย่างนี้จะสาธิตการเพิ่มการสะท้อนไปยังแอปพลิเคชันหรือกรอบงานเพื่อเพิ่มความยืดหยุ่นมากขึ้นสำหรับการปรับแต่งหรือการขยายในอนาคต เป็นที่ยอมรับว่าการใช้การสะท้อนกลับอาจยุ่งยากเล็กน้อยเมื่อเทียบกับตรรกะที่เทียบเท่าในภาษาโปรแกรมดั้งเดิม หากคุณรู้สึกว่าการเพิ่มการผูกล่าช้าตามการสะท้อนกลับในโค้ดของคุณนั้นยุ่งยากเกินไปสำหรับคุณหรือลูกค้าของคุณ (ท้ายที่สุดแล้ว พวกเขาต้องการประเภทและโค้ดของพวกเขาที่จะนำมาพิจารณาในเฟรมเวิร์กของคุณในทางใดทางหนึ่ง) ก็อาจจำเป็นเฉพาะในความยืดหยุ่นในการกลั่นกรองเท่านั้น เพื่อให้เกิดความสมดุล
การจัดการประเภทที่มีประสิทธิภาพสำหรับการทำให้เป็นอนุกรม
ตอนนี้เราได้กล่าวถึงหลักการพื้นฐานของการสะท้อนของ .NET ผ่านตัวอย่างต่างๆ แล้ว เรามาดูสถานการณ์ในโลกแห่งความเป็นจริงกันดีกว่า หากซอฟต์แวร์ของคุณโต้ตอบกับระบบอื่นผ่านบริการบนเว็บหรือเทคโนโลยีระยะไกลอื่นๆ ที่ไม่อยู่ในกระบวนการ คุณอาจประสบปัญหาในการซีเรียลไลซ์ การทำให้เป็นอนุกรมจะแปลงออบเจ็กต์ที่ใช้งานและครอบครองหน่วยความจำให้อยู่ในรูปแบบข้อมูลที่เหมาะสมสำหรับการส่งผ่านออนไลน์หรือพื้นที่จัดเก็บดิสก์
เนมสเปซ System.Xml.Serialization ใน .NET Framework มอบเอ็นจิ้นการทำให้เป็นอนุกรมที่มีประสิทธิภาพด้วย XmlSerializer ซึ่งสามารถนำอ็อบเจ็กต์ที่ได้รับการจัดการใดๆ และแปลงเป็น XML (ข้อมูล XML ยังสามารถแปลงกลับไปเป็นอินสแตนซ์อ็อบเจ็กต์ที่พิมพ์ได้ในอนาคต กระบวนการนี้ เรียกว่าดีซีเรียลไลเซชัน) คลาส XmlSerializer เป็นซอฟต์แวร์ที่มีประสิทธิภาพและพร้อมใช้งานระดับองค์กร ซึ่งจะเป็นตัวเลือกแรกของคุณหากคุณประสบปัญหาการทำให้เป็นอนุกรมในโครงการของคุณ แต่เพื่อจุดประสงค์ด้านการศึกษา เรามาสำรวจวิธีใช้งานการทำให้เป็นอนุกรม (หรืออินสแตนซ์การจัดการประเภทรันไทม์อื่นที่คล้ายกัน)
พิจารณาสิ่งนี้: คุณกำลังนำเสนอเฟรมเวิร์กที่รับอินสแตนซ์ออบเจ็กต์ของประเภทผู้ใช้ที่กำหนดเองและแปลงเป็นรูปแบบข้อมูลอัจฉริยะบางรูปแบบ ตัวอย่างเช่น สมมติว่าคุณมีออบเจ็กต์ที่อยู่ในหน่วยความจำประเภท Address ดังที่แสดงด้านล่าง:
(pseudocode)
ที่อยู่ชั้นเรียน
-
รหัสที่อยู่ ID;
ถนนสตริง เมือง;
รัฐประเภทรัฐ;
รหัสไปรษณีย์ประเภทรหัสไปรษณีย์;
-
จะสร้างการแสดงข้อมูลที่เหมาะสมสำหรับใช้ในภายหลังได้อย่างไร บางทีการแสดงข้อความธรรมดาอาจช่วยแก้ปัญหานี้ได้:
ที่อยู่: 123
Street: 1 Microsoft Way
เมือง: Redmond
State: WA
Zip: 98052
หากข้อมูลที่เป็นทางการที่จำเป็นต้องแปลงนั้นเป็นที่เข้าใจอย่างสมบูรณ์ ล่วงหน้า (เช่น เมื่อเขียนโค้ดด้วยตัวเอง) สิ่งต่างๆ จะกลายเป็นเรื่องง่าย:
foreach(Address a in AddressList)
-
Console.WriteLine(“ที่อยู่:{0}”, a.ID);
Console.WriteLine(“tStreet:{0}”, a.Street);
... //และอื่นๆ
-
อย่างไรก็ตาม สิ่งต่างๆ อาจน่าสนใจมากหากคุณไม่ทราบล่วงหน้าว่าคุณจะพบข้อมูลประเภทใดในขณะรันไทม์ คุณจะเขียนโค้ดกรอบงานทั่วไปเช่นนี้ได้อย่างไร
MyFramework.TranslateObject(อินพุตอ็อบเจ็กต์, เอาต์พุต MyOutputWriter)
ขั้นแรก คุณต้องตัดสินใจว่าสมาชิกประเภทใดมีประโยชน์สำหรับการทำให้เป็นอนุกรม ความเป็นไปได้รวมถึงการจับเฉพาะสมาชิกของประเภทเฉพาะ เช่น ประเภทของระบบดั้งเดิม หรือการจัดเตรียมกลไกสำหรับผู้สร้างประเภทเพื่อระบุว่าสมาชิกคนใดจำเป็นต้องทำให้เป็นอนุกรม เช่น การใช้คุณสมบัติแบบกำหนดเองเป็นเครื่องหมายบนสมาชิกประเภท) คุณสามารถบันทึกได้เฉพาะสมาชิกของประเภทที่ระบุเท่านั้น เช่น ประเภทระบบดั้งเดิม หรือผู้เขียนประเภทสามารถระบุสมาชิกที่ต้องการทำให้เป็นอนุกรมได้ (อาจใช้คุณสมบัติแบบกำหนดเองเป็นเครื่องหมายบนสมาชิกประเภท)
เมื่อคุณได้บันทึกสมาชิกโครงสร้างข้อมูลที่จำเป็นต้องแปลงแล้ว สิ่งที่คุณต้องทำคือเขียนตรรกะเพื่อระบุและดึงข้อมูลจากออบเจ็กต์ที่เข้ามา Reflection ช่วยยกภาระหนักที่นี่ ทำให้คุณสามารถสืบค้นทั้งโครงสร้างข้อมูลและค่าข้อมูลได้
เพื่อความเรียบง่าย มาออกแบบกลไกการแปลงน้ำหนักเบาที่รับอ็อบเจ็กต์ รับค่าทรัพย์สินสาธารณะทั้งหมด แปลงเป็นสตริงโดยการเรียก ToString โดยตรง จากนั้นจึงทำให้ค่าเป็นอนุกรม สำหรับออบเจ็กต์ที่กำหนดชื่อ "อินพุต" อัลกอริทึมจะมีลักษณะดังนี้:
เรียกอินพุต GetType เพื่อดึงอินสแตนซ์ System.Type ซึ่งอธิบายโครงสร้างพื้นฐานของอินพุต
ใช้ Type.GetProperties และพารามิเตอร์ BindingFlags ที่เหมาะสมเพื่อดึงข้อมูลคุณสมบัติสาธารณะเป็นอินสแตนซ์ PropertyInfo
คุณสมบัติจะถูกดึงข้อมูลเป็นคู่คีย์-ค่าโดยใช้ PropertyInfo.Name และ PropertyInfo.GetValue
เรียก Object.ToString ในแต่ละค่าเพื่อแปลง (ในรูปแบบพื้นฐาน) เป็นรูปแบบสตริง
แพ็คชื่อของประเภทอ็อบเจ็กต์และการรวบรวมชื่อคุณสมบัติและค่าสตริงให้อยู่ในรูปแบบซีเรียลไลซ์ที่ถูกต้อง
อัลกอริธึมนี้ช่วยลดความซับซ้อนของสิ่งต่าง ๆ ลงอย่างมากในขณะเดียวกันก็จับจุดรับโครงสร้างข้อมูลรันไทม์และเปลี่ยนให้เป็นข้อมูลที่อธิบายตัวเองได้ แต่มีปัญหาคือประสิทธิภาพ ดังที่ได้กล่าวไว้ก่อนหน้านี้ การสะท้อนกลับมีค่าใช้จ่ายสูงมากสำหรับทั้งการประมวลผลประเภทและการดึงค่า ในตัวอย่างนี้ ฉันทำการวิเคราะห์ประเภททั้งหมดกับแต่ละอินสแตนซ์ของประเภทที่ให้มา
จะเป็นอย่างไรหากเป็นไปได้ที่จะจับภาพหรือรักษาความเข้าใจของคุณเกี่ยวกับโครงสร้างของประเภทเพื่อให้คุณสามารถดึงข้อมูลได้ในภายหลังและจัดการอินสแตนซ์ใหม่ของประเภทนั้นได้อย่างมีประสิทธิภาพ กล่าวอีกนัยหนึ่ง ให้ข้ามไปยังขั้นตอนที่ 3 ในอัลกอริทึมตัวอย่าง ข่าวก็คือว่าสามารถทำได้โดยใช้คุณสมบัติใน .NET Framework เมื่อคุณเข้าใจโครงสร้างข้อมูลของประเภทแล้ว คุณสามารถใช้ CodeDom เพื่อสร้างโค้ดแบบไดนามิกที่เชื่อมโยงกับโครงสร้างข้อมูลนั้นได้ คุณสามารถสร้างแอสเซมบลีตัวช่วยที่มีคลาสตัวช่วยและวิธีการอ้างอิงประเภทขาเข้าและเข้าถึงคุณสมบัติได้โดยตรง (เช่นเดียวกับคุณสมบัติอื่น ๆ ในโค้ดที่ได้รับการจัดการ) ดังนั้นการตรวจสอบประเภทจะส่งผลต่อประสิทธิภาพเพียงครั้งเดียวเท่านั้น
ตอนนี้ฉันจะแก้ไขอัลกอริทึมนี้ ประเภทใหม่:
รับอินสแตนซ์ System.Type ที่สอดคล้องกับประเภทนี้
ใช้ตัวเข้าถึง System.Type ต่างๆ เพื่อดึงข้อมูลสคีมา (หรืออย่างน้อยชุดย่อยของสคีมาที่มีประโยชน์สำหรับการทำให้เป็นอนุกรม) เช่น ชื่อคุณสมบัติ ชื่อฟิลด์ ฯลฯ
ใช้ข้อมูลสคีมาเพื่อสร้างแอสเซมบลีตัวช่วย (ผ่าน CodeDom) ที่เชื่อมโยงกับประเภทใหม่และจัดการอินสแตนซ์ได้อย่างมีประสิทธิภาพ
ใช้โค้ดในแอสเซมบลีตัวช่วยเพื่อแยกข้อมูลอินสแตนซ์
จัดลำดับข้อมูลตามความจำเป็น
สำหรับข้อมูลขาเข้าทั้งหมดตามประเภทที่กำหนด คุณสามารถข้ามไปยังขั้นตอนที่ 4 และรับการปรับปรุงประสิทธิภาพครั้งใหญ่จากการตรวจสอบแต่ละอินสแตนซ์อย่างชัดเจน
ฉันพัฒนาไลบรารีการทำให้เป็นอนุกรมพื้นฐานที่เรียกว่า SimpleSerialization ที่ใช้อัลกอริทึมนี้โดยใช้การสะท้อนและ CodeDom (ดาวน์โหลดได้ในคอลัมน์นี้) ส่วนประกอบหลักคือคลาสชื่อ SimpleSerializer ซึ่งสร้างโดยผู้ใช้ด้วยอินสแตนซ์ของ System.Type ในตัวสร้าง อินสแตนซ์ SimpleSerializer ใหม่จะวิเคราะห์ประเภทที่กำหนดและสร้างชุดประกอบชั่วคราวโดยใช้คลาสตัวช่วย คลาสตัวช่วยจะเชื่อมโยงกับประเภทข้อมูลที่กำหนดอย่างแน่นหนา และจัดการอินสแตนซ์ราวกับว่าคุณกำลังเขียนโค้ดโดยมีความรู้เกี่ยวกับประเภทนั้นมาโดยสมบูรณ์
คลาส SimpleSerializer มีโครงร่างดังต่อไปนี้:
คลาส SimpleSerializer
-
SimpleSerializer ระดับสาธารณะ (ประเภท dataType);
โมฆะสาธารณะทำให้เป็นอนุกรม (อินพุตวัตถุ, นักเขียน SimpleDataWriter);
-
น่าทึ่งมาก! Constructor ทำการยกของหนัก: มันใช้การสะท้อนเพื่อวิเคราะห์โครงสร้างประเภทแล้วใช้ CodeDom เพื่อสร้างชุดตัวช่วย คลาส SimpleDataWriter เป็นเพียงคลังข้อมูลที่ใช้เพื่อแสดงรูปแบบการทำให้เป็นอนุกรมทั่วไป
หากต้องการซีเรียลไลซ์อินสแตนซ์คลาส Address แบบง่าย ให้ใช้รหัสเทียมต่อไปนี้เพื่อทำงานให้เสร็จสมบูรณ์:
SimpleSerializer
mySerializer = new SimpleSerializer(typeof(Address));
SimpleDataWriter Writer = new SimpleDataWriter();
mySerializer.Serialize(addressInstance,writer)
;
ขอแนะนำให้คุณลองใช้โค้ดตัวอย่างด้วยตัวเอง โดยเฉพาะไลบรารี SimpleSerialization ฉันได้เพิ่มความคิดเห็นในส่วนที่น่าสนใจของ SimpleSerializer หวังว่าจะช่วยได้ แน่นอนว่า หากคุณต้องการซีเรียลไลซ์ไลซ์ที่เข้มงวดในโค้ดที่ใช้งานจริง คุณจะต้องพึ่งพาเทคโนโลยีที่มีให้ใน .NET Framework (เช่น XmlSerializer) แต่ถ้าคุณพบว่าคุณต้องทำงานกับประเภทที่กำหนดเองในขณะรันไทม์และจัดการมันอย่างมีประสิทธิภาพ ฉันหวังว่าคุณจะใช้ไลบรารี SimpleSerialization ของฉันเป็นโซลูชันของคุณ