23-Bob: Assembly Loading va Reflection
Assembly'larni yuklash, turlar haqida ma'lumot olish, ularning instansiyalarini yaratish va a'zolariga murojaat qilish — barchasi runtime da, kompilyatsiya vaqtida hech narsa bilmasdan
Ushbu bob turlar haqida ma'lumot aniqlash, ularning instansiyalarini yaratish va kompilyatsiya vaqtida hech narsa bilmasdan a'zolariga murojaat qilish haqida. Bu bobdagi ma'lumotlar odatda dinamik kengaytiriladigan ilova (dynamically extensible application) yaratish uchun ishlatiladi. Bu shunday turdagi ilovaki, bitta kompaniya asosiy (host) ilovani yaratadi, boshqa kompaniyalar esa unga plaginlar (add-in) yaratadi. Host ilova plaginlarni kompilyatsiya yoki sinov vaqtida tekshirib ko'ra olmaydi, chunki plaginlar boshqa kompaniyalar tomonidan yaratiladi va host ilova chiqarilganidan keyin paydo bo'lishi mumkin. Shu sababli host ilova plaginlarni runtime da (ish vaqtida) aniqlashi kerak.
Dinamik kengaytiriladigan ilova CLR hosting va AppDomain'lar afzalliklaridan foydalanishi mumkin (22-bob, "CLR Hosting va AppDomains" da muhokama qilinganidek). Host ilova plagin kodini o'zining AppDomain'ida o'z xavfsizlik va konfiguratsiya sozlamalari bilan ishlatishi mumkin. Shuningdek, AppDomain'ni tushirish (unload) orqali plagin kodini xotiradan olib tashlashi mumkin. Ushbu bob oxirida men bularning barchasini — CLR hosting, AppDomain'lar, assembly yuklash, turlarni aniqlash, instansiya yaratish va reflection — qanday birlashtirish haqida gaplashaman.
.NET Framework 4.5 versiyasi uchun Microsoft yangi reflection API joriy qildi. Eski API da ko'plab kamchiliklar bor edi: LINQ'ni yaxshi qo'llab-quvvatlamasdi, ba'zi tillar uchun noto'g'ri siyosatlar kiritilgan edi, ba'zan keraksiz assembly yuklashga majbur qilardi va kamdan-kam uchraydigan muammolar uchun haddan tashqari murakkab API taklif qilardi. Yangi API bu muammolarning barchasini hal qiladi. Ammo .NET 4.5 holatida yangi API eski API kabi to'liq emas. Yangi API va ba'zi kengaytirish metodlari (System.Reflection.RuntimeReflectionExtensions klassi) bilan barcha kerakli narsani amalga oshirish mumkin. Kelajakdagi .NET versiyalarida yangi API ga qo'shimcha metodlar qo'shilishi kutilmoqda.
Albatta, desktop ilovalari uchun eski API ham mavjud, shunda mavjud kodni qayta kompilyatsiya qilganda buzilmaydi. Ammo yangi API tavsiya etilgan API hisoblanadi va shu sababli men ushbu bobda faqat yangi API ni tushuntiraman. Windows Store ilovalari uchun (orqaga moslik muammo bo'lmagan holda) yangi API dan foydalanish majburiy.
Assembly Yuklash (Assembly Loading)
Ma'lumki, just-in-time (JIT) kompilyator metod uchun Intermediate Language (IL) ni kompilyatsiya qilganda, IL kodda qaysi turlarga murojaat qilinganini ko'radi. So'ng runtime da JIT kompilyator assembly'ning TypeRef va AssemblyRef metadata jadvallaridan foydalanib, tur qaysi assembly'da aniqlanganini aniqlaydi. AssemblyRef metadata jadvali assembly identifikatsiyasini tashkil etuvchi barcha qismlarni o'z ichiga oladi — nom (kengaytmasiz yoki yo'lsiz), versiya, madaniyat (culture) va ochiq kalit tokeni. JIT kompilyator bu qismlarning barchasini oladi, ularni satrga birlashtiradi va bu identifikatsiya bilan assembly'ni AppDomain'ga yuklashga harakat qiladi (agar u hali yuklanmagan bo'lsa). Agar yuklanayotgan assembly zaif nomlangan (weakly named) bo'lsa, identifikatsiya faqat assembly nomi (versiya, madaniyat yoki ochiq kalit tokeni ma'lumotisiz).
Ichkarida CLR bu assembly'ni System.Reflection.Assembly klassining statik Load metodi yordamida yuklashga harakat qiladi. Bu metod ommaviy hujjatlashtirilgan va siz uni assembly'ni AppDomain'ga aniq yuklash uchun chaqirishingiz mumkin. Bu metod CLR'ning Win32 LoadLibrary funksiyasining ekvivalentidir.
Assembly.Load Metodi
Assembly'ning Load metodining bir nechta overload'lari mavjud. Eng ko'p ishlatiladiganlarining prototiplari quyidagicha:
public class Assembly {
public static Assembly Load(AssemblyName assemblyRef);
public static Assembly Load(String assemblyString);
// Load ning kamroq ishlatiladigan overload'lari ko'rsatilmagan
}
Ichkarida Load CLR'ga assembly'ga versiya-binding qayta yo'naltirish siyosatini (version-binding redirection policy) qo'llashni va assembly'ni global assembly cache (GAC) da, so'ng ilovaning bazaviy katalogi, private path pastki kataloglari va codebase joylashuvlarida qidirishni buyuradi. Agar siz Load'ni zaif nomlangan assembly'ni uzatib chaqirsangiz, Load assembly'ga versiya-binding qayta yo'naltirish siyosatini qo'llamaydi va CLR assembly'ni GAC'da qidirmaydi. Agar Load ko'rsatilgan assembly'ni topsa, yuklangan assembly'ni ifodalovchi Assembly obyektiga havola qaytaradi. Agar Load assembly'ni topa olmasa, System.IO.FileNotFoundException xatosini chiqaradi.
Ba'zi juda kam uchraydigan holatlarda siz ma'lum CPU arxitekturasi uchun qurilgan assembly'ni yuklashni xohlashingiz mumkin. Bu holda protsessor arxitekturasi qismini ham ko'rsatishingiz mumkin. Masalan, GAC da IL-neytral va x86-spetsifik versiya bo'lsa, CLR CPU-spetsifik versiyani afzal ko'radi. Ammo IL-neytral versiyani yuklashga majburlash uchun quyidagi satrni Assembly.Load ga uzatish mumkin:
"SomeAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=01234567890abcde, ProcessorArchitecture=MSIL"
Bugungi kunda CLR ProcessorArchitecture uchun beshta mumkin bo'lgan qiymatni qo'llab-quvvatlaydi: MSIL (Microsoft IL), x86, IA64, AMD64 va Arm.
Ba'zi dasturchilar System.AppDomain ham Load metodini taklif qilishini payqashadi. Assembly'ning statik Load metodidan farqli o'laroq, AppDomain'ning Load metodi instansiya metodi bo'lib, assembly'ni ko'rsatilgan AppDomain'ga yuklash imkonini beradi. Bu metod boshqarilmagan (unmanaged) kod tomonidan chaqirilishi uchun mo'ljallangan va u host'ga assembly'ni ma'lum bir AppDomain'ga kiritish imkonini beradi. Boshqariladigan kod dasturchilari bu metodni chaqirmasligi kerak, chunki AppDomain.Load chaqirilganda siz assembly identifikatsiyasini bildiruvchi satr uzatasiz. Keyin metod siyosat (policy) qo'llaydi va assembly'ni izlash uchun oddiy joylarni qidiradi. Assembly'ni yuklash uchun CLR ko'rsatilgan AppDomain bilan bog'liq sozlamalarni ishlatadi, chaqiruvchi AppDomain'nikini emas.
Ammo AppDomain.Load metodi assembly'ga havolani qaytaradi. System.Assembly klassi System.MarshalByRefObject dan hosil bo'lmaganligi sababli, assembly obyekti chaqiruvchi AppDomain'ga qiymat bo'yicha marshal qilinishi kerak. Ammo CLR assembly'ni topish va yuklash uchun chaqiruvchi AppDomain'ning sozlamalarini ishlatadi. Agar assembly chaqiruvchi AppDomain'ning siyosati va qidiruv joylashuvi yordamida topilmasa, FileNotFoundException xatosi chiqadi. Bu xatti-harakat odatda nomaqbul va shu sababli AppDomain.Load metodidan qochish kerak.
Assembly.LoadFrom Metodi
Ko'pgina dinamik kengaytiriladigan ilovalarda Assembly'ning Load metodi assembly'ni AppDomain'ga yuklashning afzal usuli hisoblanadi. Ammo u assembly identifikatsiyasini tashkil etuvchi barcha qismlarga ega bo'lishingizni talab qiladi. Ko'pincha dasturchilar assembly faylini qayta ishlash uchun asboblar yoki yordamchi dasturlar yozadilar (masalan, ILDasm.exe, PEVerify.exe, GACUtil.exe va h.k.). Bu asboblarning barchasi buyruq satri argumenti sifatida assembly faylining yo'l nomini (fayl kengaytmasi bilan) oladi.
Assembly'ni yo'l nomi orqali yuklash uchun Assembly'ning LoadFrom metodini chaqirasiz:
public class Assembly {
public static Assembly LoadFrom(String path);
// LoadFrom ning kamroq ishlatiladigan overload'lari ko'rsatilmagan
}
Ichkarida LoadFrom avval System.Reflection.AssemblyName'ning statik GetAssemblyName metodini chaqiradi, bu metod ko'rsatilgan faylni ochadi, AssemblyDef metadata jadvalining yozuvini topadi, assembly identifikatsiya ma'lumotlarini ajratib oladi va uni System.Reflection.AssemblyName obyektida qaytaradi (fayl ham yopiladi). So'ng LoadFrom ichkarida Assembly.Load metodini chaqiradi va unga AssemblyName obyektini uzatadi. Bu nuqtada CLR versiya-binding qayta yo'naltirish siyosatini qo'llaydi va mos assembly'ni turli joylashuvlardan qidiradi. Agar Load assembly'ni topsa, uni yuklaydi va yuklangan assembly'ni ifodalovchi Assembly obyektini qaytaradi; LoadFrom ham bu qiymatni qaytaradi. Agar Load assembly'ni topa olmasa, LoadFrom assembly'ni LoadFrom argumentida ko'rsatilgan yo'l nomi bo'yicha yuklaydi. Albatta, agar bir xil identifikatsiyaga ega assembly allaqachon yuklangan bo'lsa, LoadFrom shunchaki allaqachon yuklangan assembly'ni ifodalovchi Assembly obyektini qaytaradi.
LoadFrom metodi sizga argument sifatida URL ham uzatish imkonini beradi. Masalan:
Assembly a = Assembly.LoadFrom(@"http://wintellect.com/SomeAssembly.dll");
Internet manzilini uzatganingizda, CLR faylni yuklab oladi, uni foydalanuvchi yuklab olish keshiga o'rnatadi va faylni u yerdan yuklaydi. E'tibor bering, onlayn bo'lishingiz kerak, aks holda istisno chiqariladi. Ammo agar fayl avval yuklab olingan bo'lsa va Windows Internet Explorer oflayn rejimda ishlashga sozlangan bo'lsa, avval yuklab olingan fayl ishlatiladi va hech qanday istisno chiqarilmaydi.
Bitta mashinada bir xil identifikatsiyaga ega turli assembly'larga ega bo'lish mumkin. LoadFrom ichkarida Load ni chaqirganligi sababli, CLR ko'rsatilgan faylni emas, boshqa faylni yuklashi mumkin va bu sizga kutilmagan xatti-harakat beradi. Har bir build'da assembly versiya raqamini o'zgartirishingiz tavsiya etiladi; bu har bir versiyaning o'z identifikatsiyasiga ega bo'lishini ta'minlaydi va shu sababli LoadFrom kutilganidek ishlaydi.
Assembly.LoadFile Metodi
Microsoft Visual Studio'ning UI dizaynerlari va boshqa asboblar odatda Assembly'ning LoadFile metodidan foydalanadi. Bu metod istalgan yo'ldan assembly yuklashi mumkin va bir xil identifikatsiyaga ega assembly'ni bitta AppDomain'ga bir nechta marta yuklash uchun ishlatilishi mumkin. Bu ilova UI ga o'zgartirishlar kiritilganda va foydalanuvchi assembly'ni qayta qurganda sodir bo'lishi mumkin. LoadFile orqali assembly yuklashda CLR hech qanday bog'liqliklarni (dependency) avtomatik hal qilmaydi; kodingiz AppDomain'ning AssemblyResolve hodisasiga ro'yxatdan o'tishi va callback metodingiz har qanday bog'liq assembly'larni aniq yuklashi kerak.
ReflectionOnly Yuklash
Agar siz assembly metadata'sini reflection orqali tahlil qiladigan asbob qurayotgan bo'lsangiz va assembly ichidagi hech qanday kod bajarilmasligiga ishonch hosil qilmoqchi bo'lsangiz, assembly'ni yuklashning eng yaxshi usuli Assembly'ning ReflectionOnlyLoadFrom yoki ba'zi kamdan-kam hollarda ReflectionOnlyLoad metodini ishlatishdir:
public class Assembly {
public static Assembly ReflectionOnlyLoadFrom(String assemblyFile);
public static Assembly ReflectionOnlyLoad(String assemblyString);
// ReflectionOnlyLoad ning kamroq ishlatiladigan overload'i ko'rsatilmagan
}
ReflectionOnlyLoadFrom metodi faylni ko'rsatilgan yo'l bo'yicha yuklaydi; faylning kuchli nomi (strong-name) identifikatsiyasi olinmaydi va fayl GAC yoki boshqa joylardan qidirilmaydi. ReflectionOnlyLoad metodi esa ko'rsatilgan assembly'ni GAC, ilova bazaviy katalogi, private yo'llar va codebase'lardan qidiradi. Ammo Load metodidan farqli o'laroq, ReflectionOnlyLoad metodi versiyalash siyosatlarini qo'llamaydi, shuning uchun siz ko'rsatgan aniq versiyani olasiz.
Assembly ReflectionOnlyLoadFrom yoki ReflectionOnlyLoad bilan yuklanganda, CLR assembly ichidagi har qanday kodni bajarishni taqiqlaydi; bu metodlarning birortasi bilan yuklangan assembly'da kodni bajarish uchun har qanday urinish CLR ning InvalidOperationException chiqarishiga olib keladi. Bu metodlar asbobga kechiktirilgan imzo (delay-signed) assembly'ni, yuklashni to'xtatadigan xavfsizlik ruxsatnomalari talab qilinadigan assembly'ni yoki boshqa CPU arxitekturasi uchun yaratilgan assembly'ni yuklash imkonini beradi.
Odamlar ko'pincha assembly tushirish haqida so'rashadi. Afsuski, CLR individual assembly'larni tushirish qobiliyatini qo'llab-quvvatlamaydi. Agar CLR bunga ruxsat berganida, ilovangiz thread assembly'dagi kodni bajarayotgan metoddan qaytganda halokat (crash) qilishi mumkin edi. CLR mustahkamlik, xavfsizlik va barqarorlik haqida va bu tarzda ilovaning halokat qilishiga ruxsat berish uning maqsadlariga zid bo'ladi. Agar siz assembly'ni tushirmoqchi bo'lsangiz, uni o'z ichiga olgan butun AppDomain'ni tushirishingiz kerak. Bu 22-bobda batafsil muhokama qilingan.
ReflectionOnlyLoadFrom yoki ReflectionOnlyLoad bilan yuklangan assembly'lar ham tushirilishi mumkinday tuyuladi, chunki ulardagi kod bajarishga ruxsat berilmaydi. Ammo CLR bu ikki metod bilan yuklangan assembly'larni ham tushirishga ruxsat bermaydi. Sababi shundaki, assembly bu tarzda yuklangandan keyin, siz hali ham reflection'dan foydalanib bu assembly'lar ichida aniqlangan metadata'ga murojaat qiladigan obyektlar yaratishingiz mumkin. Assembly'ni tushirish bu obyektlarni qandaydir tarzda bekor qilishni talab qiladi. Buni kuzatish amalga oshirish va bajarish tezligi jihatidan juda qimmatga tushadi.
DLL'larni EXE ichiga joylashtirish
Ko'pgina ilovalar bitta EXE fayl va ko'plab DLL fayllardan iborat. Ilovani joylashtirish (deploy) qilganda barcha fayllar joylashtirilishi kerak. Ammo faqat bitta EXE faylni joylashtirish uchun ishlatiladigan texnika mavjud. Avval EXE faylingiz bog'liq bo'lgan va Microsoft .NET Framework'ning bir qismi sifatida kelmayotgan barcha DLL fayllarni aniqlang. So'ng bu DLL'larni Visual Studio loyihangizga qo'shing. Har bir DLL faylning xususiyatlarini ko'rsating va uning Build Action ni Embedded Resource ga o'zgartiring. Bu C# kompilyatoriga DLL fayllarni EXE faylingizga joylashtirishga olib keladi va siz faqat bitta EXE faylni joylashtira olasiz.
Runtime da CLR bog'liq DLL assembly'larni topa olmaydi, bu muammo. Buni tuzatish uchun ilovangiz initsializatsiya qilganda AppDomain'ning ResolveAssembly hodisasi bilan callback metodini ro'yxatdan o'tkazing. Callback metod kodi quyidagiga o'xshash bo'lishi kerak:
private static Assembly ResolveEventHandler(Object sender, ResolveEventArgs args) {
String dllName = new AssemblyName(args.Name).Name + ".dll";
var assem = Assembly.GetExecutingAssembly();
String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn =>
rn.EndsWith(dllName));
if (resourceName == null) return null; // Topilmadi, boshqa handler topishi mumkin
using (var stream = assem.GetManifestResourceStream(resourceName)) {
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
Endi, birinchi marta thread bog'liq DLL fayldagi turga murojaat qiladigan metodni chaqirganda, AssemblyResolve hodisasi ko'tariladi va yuqoridagi callback kodi joylashtirilgan DLL resursini topadi va Byte[] qabul qiladigan Assembly.Load metodining overload'ini chaqirib uni yuklaydi. Bu juda yaxshi texnika bo'lsa-da, e'tibor bering, bu ilovangiz runtime da foydalanadigan xotirani oshiradi.
Dinamik Kengaytiriladigan Ilova Yaratish uchun Reflection'dan Foydalanish
Ma'lumki, metadata jadvallar to'plamida saqlanadi. Assembly yoki modul qurilganda kompilyator tur aniqlash jadvali, maydon aniqlash jadvali, metod aniqlash jadvali va boshqalarni yaratadi. System.Reflection nomlar fazosi (namespace) bu metadata jadvallarini aks ettiradigan (parse qiladigan) kod yozish imkonini beradigan bir nechta turlarni o'z ichiga oladi. Amalda bu nomlar fazosidagi turlar assembly yoki modulda mavjud bo'lgan metadata ustida obyekt modelini taqdim etadi.
Bu obyekt model turlaridan foydalanib, siz tur aniqlash metadata jadvalidagi barcha turlarni osongina sanab chiqishingiz mumkin. So'ng har bir tur uchun uning bazaviy turini, u amalga oshirgan interfeyslarni va tur bilan bog'liq bayroqlarni olishingiz mumkin. System.Reflection nomlar fazosidagi qo'shimcha turlar turning maydonlari, metodlari, xususiyatlari va hodisalarini mos metadata jadvallarini tahlil qilib so'rash imkonini beradi. Siz shuningdek 18-bob ("Custom Attributes") da muhokama qilingan metadata mavjudiyatlarning istalganiga qo'llangan maxsus atributlarni aniqlashingiz mumkin.
Reflection turlarining ba'zilari va bu turlar tomonidan aniqlangan a'zolarning bir qismi maxsus ravishda CLR uchun kompilyator ishlab chiquvchilari uchun mo'ljallangan. Ilova dasturchilari odatda bu turlar va a'zolardan foydalanmaydi. Framework Class Library (FCL) hujjatlari bu turlar va a'zolarning qaysi birining kompilyator dasturchilari uchun ekanligini aniq ko'rsatmaydi, lekin agar siz barcha reflection turlar va ularning a'zolari hamma uchun emas ekanligini tushunsangiz, hujjatlar kamroq chalkash bo'ladi.
Haqiqatda juda kam ilovalar reflection turlaridan foydalanish zaruratiga ega bo'ladi. Reflection odatda turning ta'rifini tushunib, ba'zi boy funksionallikni ta'minlash uchun kerak bo'ladigan klass kutubxonalari tomonidan ishlatiladi. Masalan, FCL ning serializatsiya mexanizmi (24-bob, "Runtime Serialization"da muhokama qilingan) reflection'dan foydalanib tur qanday maydonlar aniqlaganini aniqlaydi. So'ng serializatsiya formatlash vositasi bu maydonlarning qiymatlarini oladi va ularni Internet orqali yuborish, faylga saqlash yoki vaqtinchalik xotiraga nusxalash uchun ishlatiladigan bayt oqimiga yozadi. Xuddi shunday, Visual Studio dizaynerlari reflection'dan foydalanib Web Forms yoki Windows Forms'da boshqaruv elementlarini joylashtirishda dasturlarga qaysi xususiyatlar ko'rsatilishi kerakligini aniqlaydi.
Reflection shuningdek ilova runtime da ma'lum bir assembly'dan ma'lum bir turni yuklashi kerak bo'lganda ishlatiladi. Masalan, ilova foydalanuvchidan assembly va tur nomini so'rashi mumkin. Keyin ilova assembly'ni aniq yuklashi, turni topishi, turning instansiyasini yaratishi va uning metodlarini chaqirishi mumkin. Bu foydalanish kontseptual jihatdan Win32 ning LoadLibrary va GetProcAddress funksiyalarini chaqirishga o'xshaydi. Turlarga ulash (binding) va ularning metodlarini bu tarzda chaqirish ko'pincha kechki ulash (late binding) deb ataladi. (Erta ulash — early binding — bu ilovalar tomonidan ishlatiladigan turlar va metodlar kompilyatsiya vaqtida aniqlanadigan holatdir.)
Reflection Unumdorligi (Reflection Performance)
Reflection juda kuchli mexanizmdir, chunki u sizga kompilyatsiya vaqtida bilmagan turlar va a'zolarni runtime da aniqlash va ishlatish imkonini beradi. Bu kuch ikkita asosiy kamchilik bilan birga keladi:
- Reflection kompilyatsiya vaqtidagi tur xavfsizligini yo'qotadi. Reflection satrlardan ko'p foydalanganligi sababli, siz kompilyatsiya vaqtidagi tur xavfsizligini yo'qotasiz. Masalan, agar siz
Type.GetType("int")chaqirsangiz, reflection'dan "int" deb nomlangan turni topishni so'rasangiz, kod kompilyatsiya bo'ladi, lekin runtime danullqaytaradi, chunki CLR "int" turini "System.Int32" deb biladi. - Reflection sekin. Reflection'dan foydalanganda turlar va ularning a'zolari nomlari kompilyatsiya vaqtida ma'lum emas; siz ularni runtime da har bir tur va a'zoni aniqlash uchun satr nomi yordamida topasiz. Bu shuni anglatadiki, reflection doimiy ravishda satr qidiruvlarini amalga oshiradi, chunki
System.Reflectionnomlar fazosidagi turlar assembly metadata'sini skanerlaydi. Ko'pincha satr qidiruvlari katta-kichik harfga bog'liq emas (case-insensitive), bu ularni yanada sekinlashtiradi.
A'zoni reflection orqali chaqirish ham ishlashni zaiflashtirishadi. Reflection orqali metodni chaqirish uchun avval argumentlarni massivga joylashtirishingiz kerak; ichkarida reflection ularni massivdan chiqarishi va ularni thread stekiga surishi kerak. Shuningdek, CLR metod chaqirilishidan oldin argumentlar to'g'ri ma'lumot turiga ega ekanligini tekshirishi kerak. Va nihoyat, CLR chaqiruvchining a'zoga murojaat uchun to'g'ri xavfsizlik ruxsatnomasiga ega ekanligini tekshirishi kerak.
Bu sabablarning barchasi uchun reflection'dan foydalanib maydonga murojaat qilish yoki metod/xususiyatni chaqirishdan qochish yaxshiroqdir. Agar siz turlarni dinamik aniqlash va instansiyalarni yaratadigan ilova yozayotgan bo'lsangiz, quyidagi yondashuvlardan birini tanlashingiz kerak:
- Turlarni kompilyatsiya vaqtida ma'lum bo'lgan bazaviy turdan hosil qildiring. Runtime da hosil bo'lgan turning instansiyasini yarating, havolani bazaviy tur o'zgaruvchisiga joylashtiring (cast orqali) va bazaviy tur tomonidan aniqlangan virtual metodlarni chaqiring.
- Turni kompilyatsiya vaqtida ma'lum bo'lgan interfeysni amalga oshirishga majburlang. Runtime da turning instansiyasini yarating, havolani interfeys turi o'zgaruvchisiga joylashtiring (cast orqali) va interfeys tomonidan aniqlangan metodlarni chaqiring.
Men interfeys texnikasini bazaviy tur texnikasiga qaraganda afzal ko'raman, chunki bazaviy tur texnikasi dasturga eng yaxshi ishlaydigan bazaviy turni tanlash imkonini bermaydi. Bazaviy tur texnikasi versiyalash ssenariylarida yaxshiroq ishlaydi, chunki siz doimo bazaviy turga yangi a'zo qo'shishingiz mumkin va hosil bo'lgan turlar shunchaki uni meros qilib oladi; interfeysga a'zo qo'sha olmaysiz, chunki bu interfeysni amalga oshirgan barcha turlarni kodini o'zgartirish va qayta kompilyatsiya qilishga majburlaydi.
Bu ikki texnikaning birortasidan foydalanganda, men interfeys yoki bazaviy tur o'z assembly'sida aniqlanishini tavsiya qilaman. Bu versiyalash muammolarini kamaytiradi. Batafsil ma'lumot uchun ushbu bobning keyingi "Plaginlarni Qo'llab-quvvatlaydigan Ilovani Loyihalash" bo'limiga qarang.
Assembly'da Aniqlangan Turlarni Topish
Reflection ko'pincha assembly qanday turlarni aniqlashini aniqlash uchun ishlatiladi. FCL bu ma'lumotni olish uchun ko'plab API'larni taklif qiladi. Eng ko'p ishlatiladigan API Assembly'ning ExportedTypes xususiyatidir. Quyida assembly'ni yuklaydigan va undagi barcha ommaviy eksport qilingan turlar nomlarini ko'rsatadigan kod misoli keltirilgan:
using System;
using System.Reflection;
public static class Program {
public static void Main() {
String dataAssembly = "System.Data, version=4.0.0.0, " +
"culture=neutral, PublicKeyToken=b77a5c561934e089";
LoadAssemAndShowPublicTypes(dataAssembly);
}
private static void LoadAssemAndShowPublicTypes(String assemId) {
// Assembly'ni ushbu AppDomain'ga aniq yuklash
Assembly a = Assembly.Load(assemId);
// Yuklangan assembly'dan har bir ommaviy eksport qilingan
// tur uchun bu tsiklni bajaring
foreach (Type t in a.ExportedTypes) {
// Turning to'liq nomini ko'rsating
Console.WriteLine(t.FullName);
}
}
}
Type Obyekti Aynan Nima?
E'tibor bering, oldingi kod System.Type obyektlari ketma-ketligini iteratsiya qiladi. System.Type turi tur va obyekt manipulyatsiyalari uchun boshlang'ich nuqtangizdir. System.Type obyekti tur havolasini ifodalaydi (tur aniqlashini emas).
System.Object GetType deb nomlangan ommaviy, virtual bo'lmagan instansiya metodini aniqlaydi. Bu metod chaqirilganda CLR ko'rsatilgan obyektning turini aniqlaydi va uning Type obyektiga havolani qaytaradi. AppDomain'da har bir tur uchun faqat bitta Type obyekti mavjud bo'lganligi sababli, ikki obyektning bir xil turda ekanligini tekshirish uchun tenglik va tengsizlik operatorlaridan foydalanishingiz mumkin:
private static Boolean AreObjectsTheSameType(Object o1, Object o2) {
return o1.GetType() == o2.GetType();
}
Object'ning GetType metodini chaqirishdan tashqari, FCL Type obyektini olishning bir nechta boshqa usullarini taklif qiladi:
System.TypeturiningGetTypestatik metodining bir nechta overload versiyalari mavjud. Bu metodning barcha versiyalariStringqabul qiladi. Satr turning to'liq nomini (o'z nomlar fazosi bilan birga) ko'rsatishi kerak. Agar satr shunchaki tur nomi bo'lsa, metod chaqiruvchi assembly'ni tekshirib, mos tur aniqlanganmi yoki yo'qligini ko'radi. Agar topsa, mosTypeobyektiga havolani qaytaradi. Assembly-malakali tur satrini (masalan, "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")GetTypega uzatishingiz mumkin. Bu holdaGetTypeko'rsatilgan assembly'dagi turni qidiradi (kerak bo'lsa assembly'ni yuklaydi).System.Typeturining statikReflectionOnlyGetTypemetodi ham mavjud. Bu metod oldingi nuqtadagiGetTypemetodi kabi ishlaydi, faqat tur ko'rib chiqilishi (reflected) mumkin, lekin bajarilishi mumkin emas.System.TypeInfoturi quyidagi instansiya a'zolarini taklif qiladi:DeclaredNestedTypesvaGetDeclaredNestedType.System.Reflection.Assemblyturining quyidagi instansiya a'zolari mavjud:GetType,DefinedTypesvaExportedTypes.
Ko'pgina dasturlash tillari kompilyatsiya vaqtida ma'lum bo'lgan tur nomidan Type obyektini olish imkonini beruvchi operator ham taklif qiladi. Iloji bo'lsa, yuqoridagi ro'yxatdagi metodlarning birortasi o'rniga bu operatordan foydalanishingiz kerak, chunki operator odatda tezroq kod ishlab chiqaradi. C# da bu operator typeof deb ataladi va siz uni odatda kechki ulash (late-bound) tur ma'lumotlarini erta ulash (early-bound, kompilyatsiya vaqtida ma'lum) tur ma'lumotlari bilan solishtirish uchun ishlatasiz:
private static void SomeMethod(Object o) {
// GetType runtime da obyektning turini qaytaradi (kechki ulash)
// typeof kompilyatsiya vaqtida ko'rsatilgan klassning turini qaytaradi (erta ulash)
if (o.GetType() == typeof(FileInfo)) { ... }
if (o.GetType() == typeof(DirectoryInfo)) { ... }
}
Koddagi birinchi if operatori o o'zgaruvchisi FileInfo turidagi obyektga ishora qilishini tekshiradi; u o FileInfo dan hosil bo'lgan obyektga ishora qilishini tekshirmaydi. Boshqacha aytganda, oldingi kod aniq mos kelish (exact match) uchun tekshiradi, mos kelish (compatible match) uchun emas — bu C#'ning is yoki as operatorlarini ishlatganingizda olishingiz mumkin narsa.
Yuqorida aytib o'tilganidek, Type obyekti yengil (lightweight) tur havolasini ifodalaydi. Agar turning o'zi haqida ko'proq bilmoqchi bo'lsangiz, tur aniqlashini (type definition) ifodalovchi TypeInfo obyektini olishingiz kerak. Type obyektini TypeInfo obyektiga System.Reflection.IntrospectionExtensions'ning GetTypeInfo kengaytirish metodini chaqirib aylantirish mumkin:
Type typeReference = ...; // Masalan: o.GetType() yoki typeof(Object)
TypeInfo typeDefinition = typeReference.GetTypeInfo();
Va kam foydali bo'lsa-da, TypeInfo obyektini Type obyektiga TypeInfo'ning AsType metodini chaqirib aylantirish mumkin:
TypeInfo typeDefinition = ...;
Type typeReference = typeDefinition.AsType();
TypeInfo obyektini olish CLR'ni turni aniqlagan assembly'ni yuklanganligiga ishonch hosil qilish orqali turni hal qilishga (resolve) majburlaydi. Bu qimmat operatsiya bo'lishi mumkin. Agar sizga faqat tur havolalari (Type obyektlari) kerak bo'lsa, bu xarajatdan qochish mumkin. Ammo TypeInfo obyektiga ega bo'lganingizdan keyin, uning ko'plab xususiyatlarini so'rashingiz mumkin, masalan IsPublic, IsSealed, IsAbstract, IsClass, IsValueType va boshqalar. Boshqa xususiyatlar, masalan Assembly, AssemblyQualifiedName, FullName, Module va boshqalar, turning aniqlovchi assembly yoki modul nomi va to'liq nomini qaytaradi. Siz BaseType xususiyatini so'rab turning bazaviy turiga havolani olishingiz va TypeInfo oshkor qiladigan ko'plab metodlar va xususiyatlardan tur haqida yanada ko'proq ma'lumot olishingiz mumkin.
Exception-Hosil Turlarning Iyerarxiyasini Qurish
Quyidagi kod ushbu bobda muhokama qilingan ko'plab tushunchalarni qo'llaydi: bir nechta assembly'ni AppDomain'ga yuklash va nihoyat System.Exception dan hosil bo'lgan barcha klasslarni ko'rsatish. Bu, aytgancha, 20-bob "FCL-Aniqlangan Exception Klasslari" bo'limida ko'rsatilgan exception iyerarxiyasini qurish uchun yozgan dasturimdir:
public static void Go() {
// Biz aks ettirmoqchi (reflect) bo'lgan assembly'larni aniq yuklash
LoadAssemblies();
// Barcha turlarni filtrlash va saralash
var allTypes =
(from a in AppDomain.CurrentDomain.GetAssemblies()
from t in a.ExportedTypes
where typeof(Exception).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo())
orderby t.Name
select t).ToArray();
// Meros iyerarxiya daraxtini qurish va ko'rsatish
Console.WriteLine(WalkInheritanceHierarchy(new StringBuilder(), 0, typeof(Exception),
allTypes));
}
private static StringBuilder WalkInheritanceHierarchy(
StringBuilder sb, Int32 indent, Type baseType, IEnumerable<Type> allTypes) {
String spaces = new String(' ', indent * 3);
sb.AppendLine(spaces + baseType.FullName);
foreach (var t in allTypes) {
if (t.GetTypeInfo().BaseType != baseType) continue;
WalkInheritanceHierarchy(sb, indent + 1, t, allTypes);
}
return sb;
}
private static void LoadAssemblies() {
String[] assemblies = {
"System, PublicKeyToken={0}",
"System.Core, PublicKeyToken={0}",
"System.Data, PublicKeyToken={0}",
"System.Design, PublicKeyToken={1}",
"System.DirectoryServices, PublicKeyToken={1}",
"System.Drawing, PublicKeyToken={1}",
"System.Drawing.Design, PublicKeyToken={1}",
"System.Management, PublicKeyToken={1}",
"System.Messaging, PublicKeyToken={1}",
"System.Runtime.Remoting, PublicKeyToken={0}",
"System.Security, PublicKeyToken={1}",
"System.ServiceProcess, PublicKeyToken={1}",
"System.Web, PublicKeyToken={1}",
"System.Web.RegularExpressions, PublicKeyToken={1}",
"System.Web.Services, PublicKeyToken={1}",
"System.Xml, PublicKeyToken={0}",
};
String EcmaPublicKeyToken = "b77a5c561934e089";
String MSPublicKeyToken = "b03f5f7f11d50a3a";
// System.Object ni o'z ichiga olgan assembly versiyasini olish
Version version = typeof(System.Object).Assembly.GetName().Version;
// Aks ettirmoqchi bo'lgan assembly'larni aniq yuklash
foreach (String a in assemblies) {
String AssemblyIdentity =
String.Format(a, EcmaPublicKeyToken, MSPublicKeyToken) +
", Culture=neutral, Version=" + version;
Assembly.Load(AssemblyIdentity);
}
}
Tur Instansiyasini Yaratish (Constructing an Instance of a Type)
Type-hosil obyektga havolani olganingizdan keyin, bu turning instansiyasini yaratmoqchi bo'lishingiz mumkin. FCL buning uchun bir nechta mexanizmni taklif qiladi:
System.Activator'ningCreateInstancemetodlari —Activatorklassi o'zining statikCreateInstancemetodining bir nechta overload'larini taklif qiladi. Bu metodni chaqirganingizda, siz yaratmoqchi bo'lgan obyekt turini aniqlaydiganTypega havola yokiStringni uzatishingiz mumkin.Typeqabul qiladigan versiyalar oddiyroq: siz turning konstruktori uchun argumentlar to'plamini uzatasiz va metod yangi obyektga havolani qaytaradi. Kerakli turniStringorqali ko'rsatadigan versiyalari biroz murakkabroq: avval turni aniqlaydigan assembly'ni bildiruvchi satrni, so'ng turni ham ko'rsatishingiz kerak. Bu versiyalar havola o'rnigaSystem.Runtime.Remoting.ObjectHandle(buSystem.MarshalByRefObjectdan hosil bo'lgan) qaytaradi.System.Activator'ningCreateInstanceFrommetodlari —Activatorklassi statikCreateInstanceFrommetodlarini ham taklif qiladi. Bu metodlarCreateInstancekabi ishlaydi, faqat siz turni va uning assembly'sini doimo satr parametrlari orqali ko'rsatishingiz kerak. Assembly chaqiruvchi AppDomain'gaAssembly.LoadFrommetodi (Load o'rniga) yordamida yuklanadi. Bu metodlarning hech biridaTypeparametri yo'qligi sababli, barchasiObjectHandlehavolasini qaytaradi.System.AppDomain'ning metodlari —AppDomainturi turning instansiyasini yaratadigan to'rtta instansiya metodini (bir nechta overload'lar bilan) taklif qiladi:CreateInstance,CreateInstanceAndUnwrap,CreateInstanceFromvaCreateInstanceFromAndUnwrap. Bu metodlarActivatormetodlariga o'xshash ishlaydi, faqat ular instansiya metodlaridir va obyekt qaysi AppDomain'da yaratilishini ko'rsatish imkonini beradi.System.Reflection.ConstructorInfo'ningInvokeinstansiya metodi —TypeInfoobyektiga havolani ishlatib, siz ma'lum bir konstruktorga ulashingiz va konstruktorConstructorInfoobyektiga havolani olishingiz mumkin. So'ngConstructorInfoobyektidan foydalanib uningInvokemetodini chaqirishingiz mumkin. Tur doimo chaqiruvchi AppDomain'da yaratiladi va yangi obyektga havola qaytariladi.
CLR qiymat turlaridan (value types) hech qanday konstruktor aniqlashni talab qilmaydi. Ammo bu muammo, chunki yuqoridagi ro'yxatdagi barcha mexanizmlar konstruktorni chaqirish orqali obyekt yaratadilar. Biroq, Activator.CreateInstance metodlari konstruktor chaqirmasdan qiymat turining instansiyasini yaratishga ruxsat beradi. Agar siz konstruktor chaqirmasdan qiymat turining instansiyasini yaratmoqchi bo'lsangiz, bitta Type parametri yoki Type va Boolean parametrlari qabul qiladigan CreateInstance versiyasini chaqirishingiz kerak.
Yuqorida sanab o'tilgan mexanizmlar massivlar (System.Array-hosil turlar) va delegatlar (System.MulticastDelegate-hosil turlar) bundan mustasno barcha turlar uchun obyekt yaratishga imkon beradi. Massiv yaratish uchun Array'ning statik CreateInstance metodini chaqirishingiz kerak (bir nechta overload versiyalari mavjud). CreateInstance'ning barcha versiyalariga birinchi parametr massivda bo'lishini xohlagan elementlar Type ga havoladir. Delegat yaratish uchun MethodInfo'ning CreateDelegate metodini chaqirishingiz kerak.
Generik turning instansiyasini yaratish uchun, avval ochiq turga (open type) havolani oling, so'ng Type'ning MakeGenericType metodini chaqirib, tur argumentlari sifatida ishlatmoqchi bo'lgan turlar massivini uzating. So'ng qaytarilgan Type obyektini yuqorida sanab o'tilgan turli metodlarning biriga uzating. Misol:
using System;
using System.Reflection;
internal sealed class Dictionary<TKey, TValue> { }
public static class Program {
public static void Main() {
// Generik turning tur obyektiga havolani olish
Type openType = typeof(Dictionary<,>);
// TKey=String, TValue=Int32 qo'yib yopiq turni yaratish
Type closedType = openType.MakeGenericType(typeof(String), typeof(Int32));
// Yopiq turning instansiyasini yaratish
Object o = Activator.CreateInstance(closedType);
// Ishlayotganini isbotlash
Console.WriteLine(o.GetType());
}
}
Bu kodni kompilyatsiya qilib ishlatganingizda quyidagi natijani olasiz:
Dictionary`2[System.String,System.Int32]
Plaginlarni Qo'llab-quvvatlaydigan Ilovani Loyihalash
Kengaytiriladigan ilovalar qurayotganingizda, interfeyslar markaziy element bo'lishi kerak. Siz interfeys o'rniga bazaviy klass ham ishlatishingiz mumkin, lekin umuman olganda, interfeys afzalroq, chunki u plagin ishlab chiquvchilarga o'zlarining bazaviy klassini tanlash imkonini beradi. Tasavvur qilaylik, siz ilova yozyapsiz va boshqalar siz ilova qabul qilib ishlatadigan turlar yaratishi mumkinligini xohlaysiz.
Mana bu ilovani loyihalash usuli:
- Host SDK assembly yarating — bu interfeys aniqlaydigan assembly bo'lib, uning metodlari host ilova va plaginlar o'rtasidagi aloqa mexanizmi sifatida ishlatiladi. Interfeys metodlari uchun parametr va qaytarish turlari uchun MSCorLib.dll da aniqlangan boshqa interfeyslar yoki turlardan foydalanishga harakat qiling. Agar o'z ma'lumot turlaringizni uzatmoqchi va qaytarmoqchi bo'lsangiz, ularni ham ushbu Host SDK assembly'da aniqlang. Interfeys aniqlashlari ustida kelishganingizdan so'ng, bu assembly'ga kuchli nom bering (3-bobda muhokama qilinganidek), so'ng uni paketlab hamkorlaringiz va foydalanuvchilarga joylashtiring. Nashr qilinganidan keyin bu assembly'dagi turlarga hech qanday buzuvchi o'zgarish kiritmaslikka harakat qiling.
- Plagin ishlab chiquvchilar, albatta, o'z turlarini o'zlarining Add-In assembly'sida aniqlashadi. Ularning Add-In assembly'si sizning Host SDK assembly'dagi turlarni referans qiladi. Plagin dasturchilar o'z assembly'larining yangi versiyalarini xohlagancha chiqarishi mumkin va host ilova plagin turlarni muammosiz qabul qilib ishlatishi mumkin.
- Alohida Host Application assembly yarating — bu ilovangizning turlarini o'z ichiga oladi. Bu assembly Host SDK assembly'ga referans qiladi va undagi turlarni ishlatadi. Host Application assembly'dagi kodni xohlaganingizcha o'zgartirishingiz mumkin. Plagin dasturchilar Host Application assembly'ga referans qilmaganligi sababli, siz uning yangi versiyasini istalgan vaqtda chiqarishingiz mumkin.
Bu bo'lim juda muhim ma'lumotlarni o'z ichiga oladi. Assembly'lar bo'ylab turlardan foydalanganda assembly-versiyalash masalalariga e'tibor berishingiz kerak. Assembly chegaralari bo'ylab aloqa uchun foydalanadigan turlarni alohida assembly'da ajratib qo'ying. Bu tur aniqlashlarini o'zgartirmang yoki mutatsiya qilmang. Ammo agar haqiqatan ham tur aniqlashlarini o'zgartirish kerak bo'lsa, assembly'ning versiya raqamini o'zgartirishni va nashrchi siyosat (publisher policy) faylini yaratishni unutmang.
Keling, bularning barchasini birlashtiruvchi oddiy stsenariyni ko'rib chiqaylik. Avval HostSDK.dll assembly kodi:
using System;
namespace Wintellect.HostSDK {
public interface IAddIn {
String DoSomething(Int32 x);
}
}
Ikkinchi, HostSDK interfeysini amalga oshiruvchi ikkita ommaviy turni aniqlagan AddInTypes.dll assembly kodi. Bu assembly'ni qurish uchun HostSDK.dll ga referans berilishi kerak:
using System;
using Wintellect.HostSDK;
public sealed class AddIn_A : IAddIn {
public AddIn_A() {
}
public String DoSomething(Int32 x) {
return "AddIn_A: " + x.ToString();
}
}
public sealed class AddIn_B : IAddIn {
public AddIn_B() {
}
public String DoSomething(Int32 x) {
return "AddIn_B: " + (x * 2).ToString();
}
}
Uchinchi, oddiy Host.exe assembly (konsol ilovasi) kodi. Bu assembly qurish uchun HostSDK.dll ga referans berilishi kerak. Bu host kodi foydalanish mumkin bo'lgan plagin turlarini topish uchun .dll fayl kengaytmasi bilan tugaydigan assembly'larda turlarni qidiradi va ular host EXE fayli bilan bir xil katalogga joylashtirilgan deb taxmin qiladi. Microsoft'ning Managed Extensibility Framework (MEF) men bu yerda ko'rsatgan turli mexanizmlar ustiga qurilgan va shuningdek plagin ro'yxatdan o'tish va topish mexanizmlarini taklif qiladi:
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using Wintellect.HostSDK;
public static class Program {
public static void Main() {
// Host EXE ni o'z ichiga olgan katalogni topish
String AddInDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// AddIn assembly'lari host EXE fayli bilan bir xil katalogda deb taxmin qilinadi
var AddInAssemblies = Directory.EnumerateFiles(AddInDir, "*.dll");
// Host tomonidan ishlatilishi mumkin bo'lgan Add-In turlar kolleksiyasini yaratish
var AddInTypes =
from file in AddInAssemblies
let assembly = Assembly.Load(file)
from t in assembly.ExportedTypes // Ommaviy eksport qilingan turlar
// Tur ishlatilishi mumkin agar u klass bo'lsa va IAddIn ni amalga oshirsa
where t.IsClass && typeof(IAddIn).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo())
select t;
// Initsializatsiya tugadi: host foydalanish mumkin bo'lgan Add-In'larni topdi
// Mana host Add-In obyektlarini qanday yaratishi va ishlatishi mumkin
foreach (Type t in AddInTypes) {
IAddIn ai = (IAddIn) Activator.CreateInstance(t);
Console.WriteLine(ai.DoSomething(5));
}
}
}
Bu oddiy host/plagin stsenariysi AppDomain'lardan foydalanmaydi. Ammo real hayotda siz har bir plaginni o'z AppDomain'ida o'z xavfsizlik va konfiguratsiya sozlamalari bilan yaratishingiz ehtimoldan yiroq emas. Va albatta, har bir AppDomain'ni tushirish mumkin, agar siz plaginni xotiradan olib tashlamoqchi bo'lsangiz. AppDomain chegarasida aloqa qilish uchun plagin ishlab chiquvchilarga turlarini MarshalByRefObject dan hosil qilishni aytishingiz yoki host ilovaning MarshalByRefObject'dan hosil bo'lgan o'z ichki turini aniqlash kerak bo'ladi.
Turning A'zolarini Aniqlash uchun Reflection'dan Foydalanish
Hozirgacha bu bob reflection'ning assembly yuklash, turlarni aniqlash va instansiyalarni yaratish qismlari — dinamik kengaytiriladigan ilovani qurish uchun zarur bo'lgan qismlariga e'tibor qaratdi. Yaxshi ishlash va kompilyatsiya vaqtidagi tur xavfsizligini ta'minlash uchun reflection'dan foydalanishni iloji boricha kamaytirish kerak. Dinamik kengaytiriladigan ilova ssenariyida obyekt yaratilganidan keyin host kodi odatda obyektni kompilyatsiya vaqtida ma'lum bo'lgan interfeys turi yoki bazaviy klass turiga cast qiladi; bu obyektning a'zolariga yuqori ishlash va kompilyatsiya vaqtidagi tur-xavfsiz usulda murojaat qilish imkonini beradi.
Ushbu bobning qolgan qismida men turning a'zolarini aniqlash va chaqirish uchun foydalanishingiz mumkin bo'lgan reflection'ning boshqa jihatlariga e'tibor qarataman. Turning a'zolarini aniqlash va chaqirish qobiliyati odatda dasturchi asboblari va yordamchi dasturlarni yaratish uchun ishlatiladi — ular ma'lum a'zolarning dasturlash patternlari yoki foydalanishlarini qidirish uchun assembly'ni tahlil qiladi. Bunday asboblar/yordamchi dasturlarga misollar: ILDasm.exe, FxCopCmd.exe va Visual Studio'ning Windows Forms, Windows Presentation Foundation va Web Forms dizaynerlari. Bundan tashqari, ba'zi klass kutubxonalari turning a'zolarini aniqlash va chaqirish qobiliyatidan foydalanib dasturlarga qulay funksionallik taklif qiladi. Bunday klass kutubxonalariga misollar: serializatsiya/deserializatsiya va oddiy ma'lumotlar ulashi (data binding).
Turning A'zolarini Topish (Discovering a Type's Members)
Maydonlar, konstruktorlar, metodlar, xususiyatlar, hodisalar va ichki turlar — bularning barchasi tur ichida a'zolar sifatida aniqlanishi mumkin. FCL System.Reflection.MemberInfo deb nomlangan turni o'z ichiga oladi. Bu klass barcha tur a'zolariga umumiy bo'lgan xususiyatlar to'plamini kapsullashtirgan mavhum bazaviy klassdir. MemberInfo dan bir nechta klasslar hosil bo'lgan; har bir klass ma'lum tur a'zosiga tegishli qo'shimcha xususiyatlarni kapsullashtiradi. Quyida bu turlarning iyerarxiyasi ko'rsatilgan:
MemberInfo a'zo iyerarxiyasining ildizi bo'lganligi sababli, uni biroz batafsilroq muhokama qilish mantiqiy. 23-1-Jadval MemberInfo klassi tomonidan taklif etilgan bir nechta faqat o'qish mumkin bo'lgan xususiyatlar va metodlarni ko'rsatadi. Bu xususiyatlar va metodlar barcha tur a'zolariga umumiy:
| A'zo Nomi | A'zo Turi | Tavsif |
|---|---|---|
Name | String xususiyat | A'zoning nomini qaytaradi. |
DeclaringType | Type xususiyat | A'zoni e'lon qilgan Type ni qaytaradi. |
Module | Module xususiyat | A'zoni e'lon qilgan Module ni qaytaradi. |
CustomAttributes | IEnumerable<CustomAttributeData> qaytaruvchi xususiyat | Bu a'zoga qo'llangan maxsus atributlar instansiyalarini aniqlaydigan kolleksiyani qaytaradi. Maxsus atributlar istalgan a'zoga qo'llanishi mumkin. Assembly MemberInfo dan hosil bo'lmagan bo'lsa ham, u assembly'lar bilan ishlatilishi mumkin bo'lgan bir xil xususiyatni taqdim etadi. |
Quyidagi dastur turning a'zolarini so'rash va ular haqida ma'lumot ko'rsatishni namoyish qiladi. Bu kod chaqiruvchi AppDomain'ga yuklangan barcha assembly'larda aniqlangan barcha ommaviy turlarni qayta ishlaydi. Har bir tur uchun DeclaredMembers xususiyati chaqiriladi va tur ichida aniqlangan har bir a'zoni ifodalovchi MemberInfo-hosil obyektlar kolleksiyasini qaytaradi. So'ng har bir a'zo uchun uning turi (maydon, konstruktor, metod, xususiyat va h.k.) va satr qiymati ko'rsatiladi:
using System;
using System.Reflection;
public static class Program {
public static void Main() {
// Ushbu AppDomain'ga yuklangan barcha assembly'larni ko'rib chiqish
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in assemblies) {
Show(0, "Assembly: {0}", a);
// Assembly'dagi turlarni topish
foreach (Type t in a.ExportedTypes) {
Show(1, "Type: {0}", t);
// Turning a'zolarini aniqlash
foreach (MemberInfo mi in t.GetTypeInfo().DeclaredMembers) {
String typeName = String.Empty;
if (mi is Type) typeName = "(Nested) Type";
if (mi is FieldInfo) typeName = "FieldInfo";
if (mi is MethodInfo) typeName = "MethodInfo";
if (mi is ConstructorInfo) typeName = "ConstructorInfo";
if (mi is PropertyInfo) typeName = "PropertyInfo";
if (mi is EventInfo) typeName = "EventInfo";
Show(2, "{0}: {1}", typeName, mi);
}
}
}
}
private static void Show(Int32 indent, String format, params Object[] args) {
Console.WriteLine(new String(' ', 3 * indent) + format, args);
}
}
DeclaredMembers tomonidan qaytarilgan kolleksiyaning har bir elementi iyerarxiyadagi konkret turlardan biriga havoladir. TypeInfo'ning DeclaredMembers xususiyati turning barcha a'zolarini qaytarsa-da, TypeInfo shuningdek ko'rsatilgan satr nomi uchun ma'lum a'zolarni qaytaradigan metodlarni taklif qiladi. Masalan, TypeInfo GetDeclaredNestedType, GetDeclaredField, GetDeclaredMethod, GetDeclaredProperty va GetDeclaredEvent metodlarini taklif qiladi. Bu metodlarning barchasi mos ravishda TypeInfo, FieldInfo, MethodInfo, PropertyInfo yoki EventInfo obyektiga havolani qaytaradi. GetDeclaredMethods metodi ham mavjud bo'lib, u ko'rsatilgan satr nomi bilan mos keladigan metodlarni tavsiflovchi MethodInfo obyektlari kolleksiyasini qaytaradi.
Reflection'ning obyekt modelini quyidagicha xulosa qilish mumkin: AppDomain'dan unga yuklangan assembly'larni topishingiz mumkin. Assembly'dan uni tashkil etuvchi modullarni topishingiz mumkin. Assembly yoki moduldan u aniqlaydigan turlarni topishingiz mumkin. Turdan uning ichki turlarini, maydonlarini, konstruktorlarini, metodlarini, xususiyatlarini va hodisalarini topishingiz mumkin. Nomlar fazolari bu iyerarxiyaning qismi emas, chunki ular shunchaki turlarning sintaktik to'plami hisoblanadi. Assembly'da aniqlangan barcha nomlar fazolarini ro'yxatlashni xohlasangiz, assembly'dagi barcha turlarni sanab chiqib, ularning Namespace xususiyatiga qarashingiz kerak.
Turdan shuningdek u amalga oshiradigan interfeyslarni aniqlash mumkin. Konstruktor, metod, xususiyat accessor metodi yoki hodisa add/remove metodidan GetParameters metodini chaqirib a'zo parametrlari turlari haqida xabar beruvchi ParameterInfo obyektlari massivini olishingiz mumkin. Shuningdek a'zoning qaytarish turi haqida batafsil ma'lumot olish uchun faqat o'qish mumkin ReturnParameter xususiyatidan ParameterInfo olishingiz mumkin. Generik tur yoki metod uchun tur parametrlari to'plamini olish uchun GetGenericArguments metodini chaqirishingiz mumkin. Va nihoyat, bu elementlarning istalganidan ularga qo'llangan maxsus atributlar to'plamini olish uchun CustomAttributes xususiyatini so'rashingiz mumkin.
Tur A'zolarini Chaqirish (Invoking a Type's Members)
Endi siz tur tomonidan aniqlangan a'zolarni qanday aniqlashni bilasiz, siz bu a'zolarning birini chaqirmoqchi bo'lishingiz mumkin. Chaqirish nima degan savolga a'zo turi bo'yicha javob beriladi. 23-2-Jadval har bir turdagi a'zoni chaqirish uchun qaysi metodni chaqirish kerakligini ko'rsatadi:
| A'zo Turi | A'zoni Chaqirish Metodi |
|---|---|
FieldInfo | Maydon qiymatini olish uchun GetValue, o'rnatish uchun SetValue chaqiring. |
ConstructorInfo | Turning instansiyasini yaratish va konstruktorni chaqirish uchun Invoke chaqiring. |
MethodInfo | Tur metodini chaqirish uchun Invoke chaqiring. |
PropertyInfo | Xususiyatning get accessor metodini chaqirish uchun GetValue, set accessor metodi uchun SetValue chaqiring. |
EventInfo | Hodisaning add accessor metodini chaqirish uchun AddEventHandler, remove accessor metodi uchun RemoveEventHandler chaqiring. |
PropertyInfo turi xususiyat haqida metadata ma'lumotini ifodalaydi (10-bob, "Xususiyatlar"da muhokama qilinganidek); ya'ni PropertyInfo CanRead, CanWrite va PropertyType xususiyatlarini taklif qiladi. Bu xususiyatlar xususiyatning o'qish mumkin, yozish mumkin yoki yo'qligini va xususiyatning ma'lumot turini bildiradi. PropertyInfo shuningdek faqat o'qish mumkin GetMethod va SetMethod xususiyatlariga ega bo'lib, ular xususiyatning get va set qiymatlarini olish va o'rnatish metodlarini ifodalovchi MethodInfo obyektlarini qaytaradi. PropertyInfo'ning GetValue va SetValue metodlari qulay usul sifatida mavjud; ichkarida ular mos MethodInfo obyektini chaqiradi. Parametrli xususiyatlarni (C# indexer'lari) qo'llab-quvvatlash uchun GetValue va SetValue metodlari Object[] turidagi index parametrini taklif qiladi.
EventInfo turi hodisa haqida metadata ma'lumotini ifodalaydi (11-bob, "Hodisalar"da muhokama qilinganidek). EventInfo turi hodisaning asosiy delegat turini qaytaradigan faqat o'qish mumkin EventHandlerType xususiyatini taklif qiladi. EventInfo turining faqat o'qish mumkin AddMethod va RemoveMethod xususiyatlari ham mavjud bo'lib, ular hodisaga/hodisadan delegat qo'shadigan va olib tashlaydigan metodlarga mos keladigan MethodInfo obyektlarini qaytaradi.
Quyidagi dastur reflection'dan foydalanib turning a'zolariga turli yo'llar bilan murojaat qilishni namoyish etadi. SomeType klassi turli a'zolarga ega turni ifodalaydi: private maydon (m_someField), ommaviy konstruktor (SomeType), ommaviy metod (ToString), ommaviy xususiyat (SomeProp) va ommaviy hodisa (SomeEvent). Dasturda uchta turli metod taqdim etiladi, har biri reflection'dan turli usulda foydalanadi:
BindToMemberThenInvokeTheMembermetodi a'zoga qanday ulanish va keyin uni chaqirishni namoyish etadi.BindToMemberCreateDelegateToMemberThenInvokeTheMembermetodi a'zoga qanday ulanish, keyin unga ishora qiluvchi delegat yaratish va delegat orqali chaqirishni namoyish etadi. Delegat orqali chaqirish juda tez va agar bir xil a'zoni bir xil obyektda bir nechta marta chaqirmoqchi bo'lsangiz, bu yaxshi ishlashni ta'minlaydi.UseDynamicToBindAndInvokeTheMembermetodi C#'ningdynamicprimitiv turidan (5-bob oxirida muhokama qilingan) a'zolarga murojaat qilish uchun sintaksisni soddalashtirish uchun qanday foydalanishni namoyish etadi. Bu texnika agar bir xil turdagi turli obyektlarda bir xil a'zoni chaqirmoqchi bo'lsangiz, oqilona yaxshi ishlashni berishi mumkin.
using System;
using System.Reflection;
using Microsoft.CSharp.RuntimeBinder;
using System.Linq;
// Bu klass reflection'ni namoyish etish uchun ishlatiladi
// Unda maydon, konstruktor, metod, xususiyat va hodisa bor
internal sealed class SomeType {
private Int32 m_someField;
public SomeType(ref Int32 x) { x *= 2; }
public override String ToString() { return m_someField.ToString(); }
public Int32 SomeProp {
get { return m_someField; }
set {
if (value < 1)
throw new ArgumentOutOfRangeException("value");
m_someField = value;
}
}
public event EventHandler SomeEvent;
private void NoCompilerWarnings() { SomeEvent.ToString();}
}
public static class Program {
public static void Main() {
Type t = typeof(SomeType);
BindToMemberThenInvokeTheMember(t);
Console.WriteLine();
BindToMemberCreateDelegateToMemberThenInvokeTheMember(t);
Console.WriteLine();
UseDynamicToBindAndInvokeTheMember(t);
Console.WriteLine();
}
}
private static void BindToMemberThenInvokeTheMember(Type t) {
Console.WriteLine("BindToMemberThenInvokeTheMember");
// Instansiya yaratish
Type ctorArgument = Type.GetType("System.Int32&"); // yoki typeof(Int32).MakeByRefType();
ConstructorInfo ctor = t.GetTypeInfo().DeclaredConstructors.First(
c => c.GetParameters()[0].ParameterType == ctorArgument);
Object[] args = new Object[] { 12 }; // Konstruktor argumentlari
Console.WriteLine("x before constructor called: " + args[0]);
Object obj = ctor.Invoke(args);
Console.WriteLine("Type: " + obj.GetType());
Console.WriteLine("x after constructor returns: " + args[0]);
// Maydonga o'qish va yozish
FieldInfo fi = obj.GetType().GetTypeInfo().GetDeclaredField("m_someField");
fi.SetValue(obj, 33);
Console.WriteLine("someField: " + fi.GetValue(obj));
// Metodni chaqirish
MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString");
String s = (String)mi.Invoke(obj, null);
Console.WriteLine("ToString: " + s);
// Xususiyatni o'qish va yozish
PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp");
try {
pi.SetValue(obj, 0, null);
}
catch (TargetInvocationException e) {
if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw;
Console.WriteLine("Property set catch.");
}
pi.SetValue(obj, 2, null);
Console.WriteLine("SomeProp: " + pi.GetValue(obj, null));
// Hodisaga delegat qo'shish va olib tashlash
EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent");
EventHandler eh = new EventHandler(EventCallback); // ei.EventHandlerType ga qarang
ei.AddEventHandler(obj, eh);
ei.RemoveEventHandler(obj, eh);
}
// Hodisaga qo'shiladigan callback metod
private static void EventCallback(Object sender, EventArgs e) { }
private static void BindToMemberCreateDelegateToMemberThenInvokeTheMember(Type t) {
Console.WriteLine("BindToMemberCreateDelegateToMemberThenInvokeTheMember");
// Instansiya yaratish (Konstruktorga delegat yaratib bo'lmaydi)
Object[] args = new Object[] { 12 }; // Konstruktor argumentlari
Console.WriteLine("x before constructor called: " + args[0]);
Object obj = Activator.CreateInstance(t, args);
Console.WriteLine("Type: " + obj.GetType().ToString());
Console.WriteLine("x after constructor returns: " + args[0]);
// ESLATMA: Maydonga delegat yaratib bo'lmaydi
// Metodni chaqirish
MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString");
var toString = mi.CreateDelegate<Func<String>>(obj);
String s = toString();
Console.WriteLine("ToString: " + s);
// Xususiyatni o'qish va yozish
PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp");
var setSomeProp = pi.SetMethod.CreateDelegate<Action<Int32>>(obj);
try {
setSomeProp(0);
}
catch (ArgumentOutOfRangeException) {
Console.WriteLine("Property set catch.");
}
setSomeProp(2);
var getSomeProp = pi.GetMethod.CreateDelegate<Func<Int32>>(obj);
Console.WriteLine("SomeProp: " + getSomeProp());
// Hodisaga delegat qo'shish va olib tashlash
EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent");
var addSomeEvent = ei.AddMethod.CreateDelegate<Action<EventHandler>>(obj);
addSomeEvent(EventCallback);
var removeSomeEvent = ei.RemoveMethod.CreateDelegate<Action<EventHandler>>(obj);
removeSomeEvent(EventCallback);
}
private static void UseDynamicToBindAndInvokeTheMember(Type t) {
Console.WriteLine("UseDynamicToBindAndInvokeTheMember");
// Instansiya yaratish (dynamic bilan konstruktor chaqirish mumkin emas)
Object[] args = new Object[] { 12 }; // Konstruktor argumentlari
Console.WriteLine("x before constructor called: " + args[0]);
dynamic obj = Activator.CreateInstance(t, args);
Console.WriteLine("Type: " + obj.GetType().ToString());
Console.WriteLine("x after constructor returns: " + args[0]);
// Maydonga o'qish va yozish
try {
obj.m_someField = 5;
Int32 v = (Int32)obj.m_someField;
Console.WriteLine("someField: " + v);
}
catch (RuntimeBinderException e) {
// Biz bu yerga tushamiz chunki maydon private
Console.WriteLine("Failed to access field: " + e.Message);
}
// Metodni chaqirish
String s = (String)obj.ToString();
Console.WriteLine("ToString: " + s);
// Xususiyatni o'qish va yozish
try {
obj.SomeProp = 0;
}
catch (ArgumentOutOfRangeException) {
Console.WriteLine("Property set catch.");
}
obj.SomeProp = 2;
Int32 val = (Int32)obj.SomeProp;
Console.WriteLine("SomeProp: " + val);
// Hodisaga delegat qo'shish va olib tashlash
obj.SomeEvent += new EventHandler(EventCallback);
obj.SomeEvent -= new EventHandler(EventCallback);
}
Bu kodni kompilyatsiya qilib ishlatganingizda quyidagi natijani ko'rasiz:
BindToMemberThenInvokeTheMember
x before constructor called: 12
Type: SomeType
x after constructor returns: 24
someField: 33
ToString: 33
Property set catch.
SomeProp: 2
BindToMemberCreateDelegateToMemberThenInvokeTheMember
x before constructor called: 12
Type: SomeType
x after constructor returns: 24
ToString: 0
Property set catch.
SomeProp: 2
UseDynamicToBindAndInvokeTheMember
x before constructor called: 12
Type: SomeType
x after constructor returns: 24
Failed to access field: 'SomeType.m_someField' is inaccessible due to its protection level
ToString: 0
Property set catch.
SomeProp: 2
E'tibor bering, SomeType'ning konstruktori Int32ni reference bo'yicha qabul qiladi. Oldingi kod konstruktorni qanday chaqirishni va o'zgartirilgan Int32 qiymatini konstruktor qaytganidan keyin tekshirishni ko'rsatadi. BindToMemberThenInvokeTheMember metodining yuqori qismida men Type'ning GetType metodini chaqirib "System.Int32&" satrini uzatish orqali buni qanday amalga oshirishni ko'rsataman. Satrdagi ampersand (&) reference bo'yicha uzatilayotgan parametrni aniqlash imkonini beradi. Bu ampersand Backus-Naur Form grammatikasining bir qismi bo'lib, uni FCL hujjatlarida topishingiz mumkin. Shuningdek, Type'ning MakeByRefType metodini ishlatib xuddi shu narsani amalga oshirish mumkinligini ko'rsataman.
Binding Handle'lar Yordamida Jarayon Xotira Iste'molini Kamaytirish
Ko'pgina ilovalar turlar to'plamiga (Type obyektlari) yoki tur a'zolariga (MemberInfo-hosil obyektlar) ulanadi va bu obyektlarni qandaydir kolleksiyada saqlaydi. Keyin ilova ma'lum obyekt uchun kolleksiyani qidiradi va uni chaqiradi. Bu yaxshi yondashuv, bitta kichik muammo bundan mustasno: Type va MemberInfo-hosil obyektlar ko'p xotira talab qiladi. Agar ilova bu obyektlarning juda ko'pini saqlasa va ularni vaqti-vaqti bilan chaqirsa, ilovaning xotira iste'moli keskin ortadi va ilovaning ishlashiga salbiy ta'sir ko'rsatadi.
Ichkarida CLR bu ma'lumotlarni ifodalashning yanada ixcham usulidan foydalanadi. CLR bu obyektlarni ilovalarimiz uchun faqat dasturchilar uchun ishni osonlashtirish maqsadida yaratadi. CLR'ning o'zi bu katta obyektlarga muhtoj emas. Juda ko'p Type va MemberInfo-hosil obyektlarni saqlayotgan/keshlayotgan dasturchilar runtime handle'lardan foydalanib ish to'plamini (working set) kamaytirishga harakat qilishlari mumkin. FCL uchta runtime handle turini belgilaydi (hammasini System nomlar fazosida aniqlangan): RuntimeTypeHandle, RuntimeFieldHandle va RuntimeMethodHandle. Bu turlarning barchasi faqat bitta maydonga ega qiymat turlari — IntPtr; bu bu turlar instansiyalarini xotira jihatidan arzon qiladi. IntPtr maydoni AppDomain'ning yuklagich heapidagi tur, maydon yoki metodni ko'rsatuvchi handle hisoblanadi.
Og'ir vaznli Type/MemberInfo obyektini yengil runtime handle instansiyasiga va aksincha aylantirish quyidagi konversiya metodlari va xususiyatlardan foydalanib oson:
TypeobyektniRuntimeTypeHandlega aylantirish uchunType'ning statikGetTypeHandlemetodini chaqiring vaTypeobyektga havolani uzating.RuntimeTypeHandle'niTypeobyektga aylantirish uchunType'ning statikGetTypeFromHandlemetodini chaqiring vaRuntimeTypeHandle'ni uzating.FieldInfoobyektniRuntimeFieldHandlega aylantirish uchunFieldInfo'ning instansiyaFieldHandlefaqat o'qish xususiyatini so'rang.RuntimeFieldHandle'niFieldInfoobyektga aylantirish uchunFieldInfo'ning statikGetFieldFromHandlemetodini chaqiring.MethodInfoobyektniRuntimeMethodHandlega aylantirish uchunMethodInfo'ning instansiyaMethodHandlefaqat o'qish xususiyatini so'rang.RuntimeMethodHandle'niMethodInfoobyektga aylantirish uchunMethodInfo'ning statikGetMethodFromHandlemetodini chaqiring.
Quyidagi dastur ko'p MethodInfo obyektlarni oladi, ularni RuntimeMethodHandle instansiyalariga aylantiradi va ish to'plami farqini ko'rsatadi:
using System;
using System.Reflection;
using System.Collections.Generic;
public sealed class Program {
private const BindingFlags c_bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance |
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
public static void Main() {
// Reflection ishidan oldin heap hajmi
Show("Before doing anything");
// MSCorlib.dll dagi barcha metodlar uchun MethodInfo keshini qurish
List<MethodBase> methodInfos = new List<MethodBase>();
foreach (Type t in typeof(Object).Assembly.GetExportedTypes()) {
// Generik turlarni o'tkazib yuborish
if (t.IsGenericTypeDefinition) continue;
MethodBase[] mb = t.GetMethods(c_bf);
methodInfos.AddRange(mb);
}
// Barcha metodlarga ulangandan keyin heap hajmini ko'rsatish
Console.WriteLine("# of methods={0:N0}", methodInfos.Count);
Show("After building cache of MethodInfo objects");
// Barcha MethodInfo uchun RuntimeMethodHandle keshini qurish
List<RuntimeMethodHandle> methodHandles =
methodInfos.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle);
Show("Holding MethodInfo and RuntimeMethodHandle cache");
GC.KeepAlive(methodInfos); // Keshni erta GC qilinishdan saqlash
methodInfos = null; // Keshni GC qilishga ruxsat berish
Show("After freeing MethodInfo objects");
methodInfos = methodHandles.ConvertAll<MethodBase>(
rmh => MethodBase.GetMethodFromHandle(rmh));
Show("Size of heap after re-creating MethodInfo objects");
GC.KeepAlive(methodHandles); // Keshni erta GC qilinishdan saqlash
GC.KeepAlive(methodInfos); // Keshni erta GC qilinishdan saqlash
methodHandles = null; // Keshni GC qilishga ruxsat berish
methodInfos = null; // Keshni GC qilishga ruxsat berish
Show("After freeing MethodInfos and RuntimeMethodHandles");
}
}
Bu dasturni kompilyatsiya qilib ishlatganda quyidagi natija olindi:
Heap size= 85,000 - Before doing anything
# of methods=48,467
Heap size= 7,065,632 - After building cache of MethodInfo objects
Heap size= 7,453,496 - Holding MethodInfo and RuntimeMethodHandle cache
Heap size= 6,732,704 - After freeing MethodInfo objects
Heap size= 7,372,704 - Size of heap after re-creating MethodInfo objects
Heap size= 192,232 - After freeing MethodInfos and RuntimeMethodHandles
Ko'rib turganingizdek, MethodInfo obyektlari juda ko'p xotira egallaydi (taxminan 7 MB). Ularni RuntimeMethodHandle larga aylantirish va MethodInfo larni bo'shatish xotira iste'molini sezilarli darajada kamaytiradi (~6.7 MB gacha). Keyin RuntimeMethodHandle lardan MethodInfo larni qayta yaratish mumkin, lekin bu ham xotira talab qiladi. Va nihoyat, ikkalasini ham bo'shatganda heap hajmi deyarli boshlang'ich holatiga qaytadi (~192 KB).
Ushbu bobda siz quyidagi mavzularni o'rgandingiz:
- Assembly yuklash:
Assembly.Load,LoadFrom,LoadFilevaReflectionOnlyLoadFrom/ReflectionOnlyLoadmetodlari o'rtasidagi farqlar va ulardan qachon foydalanish kerakligi. - Reflection: metadata jadvallarni tahlil qilib turlar va ularning a'zolari haqida runtime da ma'lumot olishning kuchli mexanizmi.
- Dinamik kengaytiriladigan ilovalar: interfeyslar, assembly yuklash va reflection'ni birlashtirib plaginlarni qo'llab-quvvatlaydigan ilovalar yaratish.
- Reflection unumdorligi: reflection sekin va tur xavfsizligini yo'qotadi; iloji boricha interfeyslar yoki bazaviy turlardan foydalaning.
- Tur instansiyalarini yaratish:
Activator.CreateInstance,ConstructorInfo.Invokeva boshqa mexanizmlar. - A'zolarni aniqlash va chaqirish:
MemberInfoiyerarxiyasi,FieldInfo,MethodInfo,PropertyInfo,EventInfo. - Binding handle'lar:
RuntimeTypeHandle,RuntimeFieldHandle,RuntimeMethodHandleyordamida xotira iste'molini kamaytirish.