18-Bob: Custom Atributlar
Custom atributlar, o'z atribut klasslarini aniqlash, atribut konstruktorlari, reflection orqali atributlarni aniqlash, atributlarni solishtirish va shartli atribut klasslari
Ushbu bobda men Microsoft .NET Framework taklif qiladigan eng innovatsion xususiyatlardan birini muhokama qilaman: custom atributlar (maxsus atributlar). Custom atributlar sizga kod konstruktsiyalaringizni deklarativ ravishda izoh (annotate) qilish imkonini beradi. Custom atributlar ma'lumotni aniqlash va deyarli har qanday metama'lumot jadvali yozuviga qo'llash imkonini beradi, bu esa maxsus xususiyatlarni faollashtiradi. Bu kengaytiriladigan metama'lumotni ish vaqtida (runtime) so'rash mumkin, bu esa turli .NET Framework texnologiyalari (Windows Forms, WPF, WCF va boshqalar) kodning bajarilish usulini dinamik ravishda o'zgartirishiga imkon beradi. Ko'rib turganingizdek, bu texnologiyalarning barchasi custom atributlardan foydalanadi va dasturchilar o'z niyatlarini kodda juda oson ifodalashlari mumkin. Custom atributlarni yaxshi tushunish har qanday .NET Framework dasturchisi uchun zarur.
Ushbu bobda quyidagi bo'limlar mavjud:
- Custom Atributlardan Foydalanish . . . . . . . . . . . . . . . . . . . . 421
- O'z Atribut Klassingizni Aniqlash . . . . . . . . . . . . . . . . . . . . . 425
- Atribut Konstruktori va Maydon/Xususiyat Ma'lumot Turlari . . . . 428
- Custom Atribut Ishlatilishini Aniqlash . . . . . . . . . . . . . . . . . . 430
- Ikki Atribut Instansiyasini Bir-biriga Solishtirish . . . . . . . . . . 434
- Custom Atribut Ishlatilishini Attribute-dan Hosil Bo'lgan Obyektlarsiz Aniqlash . . 437
- Shartli Atribut Klasslari . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
Custom Atributlardan Foydalanish
Custom atributlar haqida birinchi navbatda tushunishingiz kerak bo'lgan narsa shuki, ular shunchaki qo'shimcha ma'lumotni maqsad (target) bilan bog'lash usuli. Kompilyator bu qo'shimcha ma'lumotni boshqariladigan modulning metama'lumotlariga yozadi. Ko'pgina atributlar kompilyator uchun hech qanday ma'noga ega emas; kompilyator shunchaki manba kodidagi atributlarni aniqlaydi va tegishli metama'lumotni yozadi.
.NET Framework Class Library (FCL) o'zingizning manba kodingizga qo'llanishi mumkin bo'lgan so'zma-so'z yuzlab custom atributlarni aniqlaydi. Mana ba'zi misollar:
DllImportatributini metodga qo'llash CLR ga metodning amalga oshirilishi aslida ko'rsatilgan DLL dagi boshqarilmaydigan kodda ekanligini bildiradi.Serializableatributini turga qo'llash serializatsiya formatterlariga tur instansiyasining maydonlari serializatsiya va deserializatsiya qilinishi mumkinligini bildiradi.AssemblyVersionatributini assembliyaga qo'llash assembliyaning versiya raqamini o'rnatadi.Flagsatributini sanab o'tiladigan (enumerated) turga qo'llash sanab o'tiladigan turning bit bayroqlari to'plami sifatida ishlashiga olib keladi.
Quyida ko'plab atributlar qo'llanilgan ba'zi C# kodi keltirilgan. C# da custom atributni maqsadga qo'llash uchun siz atributni to'rtburchak qavslar ichida maqsad oldidan joylashtirasiz. Bu kod nima qilishini tushunish hozir muhim emas. Men faqat atributlar qanday ko'rinishini ko'rishingizni xohlayman.
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal sealed class OSVERSIONINFO {
public OSVERSIONINFO() {
OSVersionInfoSize = (UInt32) Marshal.SizeOf(this);
}
public UInt32 OSVersionInfoSize = 0;
public UInt32 MajorVersion = 0;
public UInt32 MinorVersion = 0;
public UInt32 BuildNumber = 0;
public UInt32 PlatformId = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public String CSDVersion = null;
}
internal sealed class MyClass {
[DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean GetVersionEx([In, Out] OSVERSIONINFO ver);
}
Bu holda, StructLayout atributi OSVERSIONINFO klassiga qo'llanilgan, MarshalAs atributi CSDVersion maydoniga qo'llanilgan, DllImport atributi GetVersionEx metodiga qo'llanilgan, va In hamda Out atributlari GetVersionEx ning ver parametriga qo'llanilgan. Har bir dasturlash tili custom atributni maqsadga qo'llash uchun ishlatuvchi sintaksisni belgilaydi. Masalan, Microsoft Visual Basic .NET burchakli qavslar (<, >) talab qiladi, to'rtburchak qavslar o'rniga.
Atribut Maqsadlari (Targets)
CLR atributlarni faylning metama'lumotlarida ifodalanishi mumkin bo'lgan deyarli har qanday narsaga qo'llash imkonini beradi. Eng ko'p hollarda, atributlar quyidagi ta'rif jadvallari (definition tables) yozuvlariga qo'llaniladi: TypeDef (klasslar, strukturalar, sanab o'tishlar, interfeyslar va delegatlar), MethodDef (konstruktorlarni ham o'z ichiga olgan), ParamDef, FieldDef, PropertyDef, EventDef, AssemblyDef va ModuleDef. Aniqrog'i, C# sizga atributni faqat quyidagi maqsadlardan birini aniqlaydigan manba kodiga qo'llash imkonini beradi: assembly, modul, tur (klass, struct, enum, interfeys, delegat), maydon (field), metod (konstruktorlarni ham o'z ichiga olgan), metod parametri, metod qaytarish qiymati, xususiyat (property), hodisa (event) va generik tur parametri.
Atributni qo'llashda, C# sizga atribut qaysi maqsadga qo'llanilishini aniq ko'rsatish uchun prefiks belgilash imkonini beradi. Quyidagi kod barcha mumkin bo'lgan prefikslarni ko'rsatadi. Ko'p hollarda, agar siz prefiksni tashlab qoldirsangiz, kompilyator hali ham atributning qaysi maqsadga qo'llanilishini aniqlashi mumkin, oldingi misolda ko'rsatilganidek. Ba'zi hollarda, kompilyatorga niyatlaringizni aniq bildirish uchun prefiksni belgilash shart. Quyidagi kodda kursiv bilan ko'rsatilgan prefikslar majburiydir.
using System;
[assembly: SomeAttr] // Assembly ga qo'llanilgan
[module: SomeAttr] // Modul ga qo'llanilgan
[type: SomeAttr] // Tur ga qo'llanilgan
internal sealed class SomeType<[typevar: SomeAttr] T> { // Generik tur parametriga qo'llanilgan
[field: SomeAttr] // Maydon ga qo'llanilgan
public Int32 SomeField = 0;
[return: SomeAttr] // Qaytarish qiymatiga qo'llanilgan
[method: SomeAttr] // Metod ga qo'llanilgan
public Int32 SomeMethod(
[param: SomeAttr] // Parametr ga qo'llanilgan
Int32 SomeParam) { return SomeParam; }
[property: SomeAttr] // Xususiyat ga qo'llanilgan
public String SomeProp {
[method: SomeAttr] // get aksessor metodiga qo'llanilgan
get { return null; }
}
[event: SomeAttr] // Hodisa ga qo'llanilgan
[field: SomeAttr] // Kompilyator yaratgan maydon ga qo'llanilgan
[method: SomeAttr] // Kompilyator yaratgan add & remove metodlari ga qo'llanilgan
public event EventHandler SomeEvent;
}
Atribut Nima Ekanligini Tushunish
Endi siz custom atributni qanday qo'llashni bilasiz, keling, atribut aslida nima ekanligini aniqlaylik. Custom atribut shunchaki klassning instansiyasidir. Common Language Specification (CLS) ga muvofiqlik uchun, custom atribut klasslari to'g'ridan-to'g'ri yoki bilvosita ommaviy abstrakt System.Attribute klassidan hosil bo'lishi kerak. C# faqat CLS ga mos atributlarga ruxsat beradi. .NET Framework SDK hujjatlarini ko'rib chiqsangiz, quyidagi klasslar aniqlangan: StructLayoutAttribute, MarshalAsAttribute, DllImportAttribute, InAttribute va OutAttribute. Bu klasslarning barchasi System.Runtime.InteropServices nomlar fazosida aniqlangan bo'lib, lekin atribut klasslari istalgan nomlar fazosida aniqlanishi mumkin. Batafsilroq tekshirsangiz, bu klasslarning barchasi System.Attribute dan hosil bo'lganini ko'rasiz, chunki barcha CLS ga mos atribut klasslari shunday bo'lishi kerak.
Manba kodida maqsadga atributni qo'llashda, C# kompilyatori dasturlash yozuvini kamaytirish va manba kodining o'qilishini yaxshilash uchun Attribute qo'shimchasini tushirib qoldirishga ruxsat beradi. Ushbu bobdagi kod misollari bu C# qulayligidan foydalanadi. Masalan, mening manba kodim [DllImportAttribute(...)] o'rniga [DllImport(...)] ni o'z ichiga oladi.
Atribut Sintaksisi
Oldinroq aytib o'tganimdek, atribut klassning instansiyasidir. Klassda instansiyalar yaratilishi uchun ommaviy (public) konstruktor bo'lishi kerak. Shuning uchun, siz atributni maqsadga qo'llaganingizda, sintaksis klassning instansiya konstruktorlaridan birini chaqirishga o'xshaydi. Bundan tashqari, til atribut bilan bog'langan har qanday ommaviy maydon yoki xususiyatlarni o'rnatish imkonini beruvchi maxsus sintaksisni taklif qilishi mumkin. Keling, misolga qaraylik. DllImport atributining GetVersionEx metodiga qo'llanilishini eslang.
[DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true)]
Bu satrning sintaksisi sizga g'alati ko'rinishi kerak, chunki siz konstruktorni chaqirishda bunday sintaksisni hech qachon ishlata olmaysiz. Agar siz hujjatlarda DllImportAttribute klassini ko'rib chiqsangiz, uning konstruktori bitta String parametrni talab qilishini ko'rasiz. Bu misolda "Kernel32" shu parametr uchun uzatilmoqda. Konstruktor parametrlari pozitsion parametrlar (positional parameters) deb ataladi va ular majburiydir; atribut qo'llanilganda parametr ko'rsatilishi shart.
Pozitsion va Nomlangan Parametrlar
Boshqa ikkita "parametr" nima? Bu maxsus sintaksis sizga DllImportAttribute obyekti yaratilgandan keyin uning istalgan ommaviy maydon yoki xususiyatlarini o'rnatishga imkon beradi. Bu misolda, DllImportAttribute obyekti yaratilganda va "Kernel32" konstruktorga uzatilganda, obyektning ommaviy instansiya maydonlari CharSet va SetLastError mos ravishda CharSet.Auto va true qiymatlariga o'rnatiladi. Maydonlar yoki xususiyatlarni o'rnatadigan "parametrlar" nomlangan parametrlar (named parameters) deb ataladi va ular ixtiyoriydir, chunki ular atributning instansiyasini qo'llashda ko'rsatilishi shart emas.
Biroz keyin men DllImportAttribute klassining instansiyasi aslida qanday yaratilishini tushuntiraman.
Bir Nechta Atributni Qo'llash
Shuningdek, bitta maqsadga bir nechta atribut qo'llash mumkin ekanligini unutmang. Masalan, ushbu bobning birinchi dastur ro'yxatida GetVersionEx metodining ver parametriga In va Out atributlari qo'llanilgan. Bitta maqsadga bir nechta atribut qo'llashda, atributlar tartibining ahamiyati yo'qligini bilib qo'ying. Shuningdek, C# da har bir atribut to'rtburchak qavslarga olinishi mumkin, yoki bir nechta atributlar bitta to'rtburchak qavslar to'plami ichida vergul bilan ajratilishi mumkin. Agar atribut klassining konstruktori hech qanday parametr qabul qilmasa, qavslar ixtiyoriy. Nihoyat, oldinroq aytib o'tilganidek, Attribute qo'shimchasi ham ixtiyoriy. Quyidagi satrlar bir xil ishlaydi va bir nechta atributni qo'llashning barcha mumkin bo'lgan usullarini ko'rsatadi.
[Serializable][Flags]
[Serializable, Flags]
[FlagsAttribute, SerializableAttribute]
[FlagsAttribute()][Serializable()]
O'z Atribut Klassingizni Aniqlash
Siz atribut System.Attribute dan hosil bo'lgan klassning instansiyasi ekanligini bilasiz va siz atributni qanday qo'llashni ham bilasiz. Keling, endi o'zingizning custom atribut klasslaringizni qanday aniqlashni ko'rib chiqaylik. Aytaylik, siz sanab o'tiladigan turlarga (enumerated types) bit bayrog'i qo'llab-quvvatlashini qo'shish uchun mas'ul Microsoft xodimisiz. Buning uchun birinchi navbatda FlagsAttribute klassini aniqlashingiz kerak.
namespace System {
public class FlagsAttribute : System.Attribute {
public FlagsAttribute() {
}
}
}
E'tibor bering, FlagsAttribute klassi Attribute dan meros oladi; bu FlagsAttribute klassini CLS ga mos custom atribut qiladi. Bundan tashqari, klassning nomi Attribute qo'shimchasiga ega; bu standart konvensiyaga amal qiladi, lekin majburiy emas. Nihoyat, barcha mavhum bo'lmagan (non-abstract) atributlar kamida bitta ommaviy konstruktorga ega bo'lishi kerak. Oddiy FlagsAttribute konstruktori hech qanday parametr qabul qilmaydi va hech narsa qilmaydi.
Atribut turi klass bo'lsa-da, siz atributni mantiqiy holat konteyneri deb hisoblashingiz kerak. Ya'ni, atribut turi klass bo'lsa-da, klass oddiy bo'lishi kerak. Klass atributning majburiy (yoki pozitsion) holat ma'lumotlarini qabul qiluvchi faqat bitta ommaviy konstruktor taklif qilishi, va klass atributning ixtiyoriy (yoki nomlangan) holat ma'lumotlarini qabul qiluvchi ommaviy maydonlar/xususiyatlar taklif qilishi kerak. Klass hech qanday ommaviy metodlar, hodisalar yoki boshqa a'zolar taklif qilmasligi kerak.
Umuman olganda, men har doim atributlar uchun ommaviy maydonlardan foydalanishni oldini olaman va hali ham ularni atributlar uchun qo'llamayman. Xususiyatlardan foydalanish ancha yaxshiroq, chunki agar siz atribut klassi qanday amalga oshirilishini o'zgartirishga qaror qilsangiz, bu ko'proq moslashuvchanlikni ta'minlaydi.
Hozircha, FlagsAttribute klassining instansiyalari istalgan maqsadga qo'llanilishi mumkin, lekin bu atribut haqiqatan ham faqat sanab o'tiladigan turlarga qo'llanilishi kerak. Uni xususiyat yoki metodga qo'llash mantiqiy emas. Kompilyatorga bu atributni qonuniy ravishda qayerda qo'llash mumkinligini aytish uchun, siz System.AttributeUsageAttribute klassining instansiyasini atribut klassiga qo'llaysiz. Mana yangilangan kod.
namespace System {
[AttributeUsage(AttributeTargets.Enum, Inherited = false)]
public class FlagsAttribute : System.Attribute {
public FlagsAttribute() {
}
}
}
Bu yangi versiyada men AttributeUsageAttribute instansiyasini atributga qo'lladim. Axir, atribut turi shunchaki klassdir va klassga atributlar qo'llanilishi mumkin. AttributeUsage atributi oddiy klass bo'lib, sizga kompilyatorga custom atributingiz qayerda qonuniy ravishda qo'llanilishi mumkinligini ko'rsatish imkonini beradi. Barcha kompilyatorlar ushbu atribut uchun ichki qo'llab-quvvatlashga ega va foydalanuvchi tomonidan aniqlangan custom atribut noto'g'ri maqsadga qo'llanilganda xatoliklarni yaratadi. Ushbu misolda, AttributeUsage atributi Flags atributining instansiyalari faqat sanab o'tiladigan tur maqsadlariga qo'llanilishi mumkinligini belgilaydi.
AttributeUsageAttribute Klassi
Barcha atributlar shunchaki turlar bo'lganligi sababli, siz AttributeUsageAttribute klassini osonlikcha tushunishingiz mumkin. Mana FCL manba kodida klass qanday ko'rinishda.
[Serializable]
[AttributeUsage(AttributeTargets.Class, Inherited=true)]
public sealed class AttributeUsageAttribute : Attribute {
internal static AttributeUsageAttribute Default =
new AttributeUsageAttribute(AttributeTargets.All);
internal Boolean m_allowMultiple = false;
internal AttributeTargets m_attributeTarget = AttributeTargets.All;
internal Boolean m_inherited = true;
// Bu yagona ommaviy konstruktor
public AttributeUsageAttribute(AttributeTargets validOn) {
m_attributeTarget = validOn;
}
internal AttributeUsageAttribute(AttributeTargets validOn,
Boolean allowMultiple, Boolean inherited) {
m_attributeTarget = validOn;
m_allowMultiple = allowMultiple;
m_inherited = inherited;
}
public Boolean AllowMultiple {
get { return m_allowMultiple; }
set { m_allowMultiple = value; }
}
public Boolean Inherited {
get { return m_inherited; }
set { m_inherited = value; }
}
public AttributeTargets ValidOn {
get { return m_attributeTarget; }
}
}
Ko'rib turganingizdek, AttributeUsageAttribute klassi atributingizning qayerda qonuniy ravishda qo'llanilishi mumkinligini ko'rsatish uchun bit bayroqlarini uzatish imkonini beruvchi ommaviy konstruktorga ega. System.AttributeTargets sanab o'tiladigan turi FCL da quyidagicha aniqlangan.
AttributeTargets Sanab O'tish Turi
[Flags, Serializable]
public enum AttributeTargets {
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
ReturnValue = 0x2000,
GenericParameter = 0x4000,
All = Assembly | Module | Class | Struct | Enum |
Constructor | Method | Property | Field | Event |
Interface | Parameter | Delegate | ReturnValue |
GenericParameter
}
AllowMultiple va Inherited Xususiyatlari
AttributeUsageAttribute klassi atribut klassiga qo'llanilganda ixtiyoriy ravishda o'rnatilishi mumkin bo'lgan ikkita qo'shimcha ommaviy xususiyat taklif qiladi: AllowMultiple va Inherited.
Ko'pgina atributlar uchun ularni bitta maqsadga bir nechta marta qo'llash mantiqiy emas. Masalan, Flags yoki Serializable atributlarini bitta maqsadga bir nechta marta qo'llashdan hech qanday foyda yo'q. Aslida, agar siz quyidagi kodni kompilyatsiya qilishga harakat qilsangiz, kompilyator quyidagi xabarni beradi: error CS0579: Duplicate 'Flags' attribute.
[Flags][Flags]
internal enum Color {
Red
}
Ammo ba'zi atributlar uchun atributni bitta maqsadga bir nechta marta qo'llash mantiqiy. FCL da ConditionalAttribute atribut klassi o'zining bir nechta instansiyalarini bitta maqsadga qo'llashga ruxsat beradi. Agar siz AllowMultiple ni aniq true ga o'rnatmasangiz, atributingiz tanlangan maqsadga bir martadan ko'p qo'llanilishi mumkin emas.
AttributeUsageAttribute ning boshqa xususiyati Inherited, atribut bazaviy klassga va qayta aniqlangan (overriding) metodlarga qo'llanilganda hosil bo'lgan klasslarga ham qo'llanilishi kerakligini ko'rsatadi. Quyidagi kod atribut meros qilib olinishi nimani anglatishini ko'rsatadi.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited=true)]
internal class TastyAttribute : Attribute {
}
[Tasty][Serializable]
internal class BaseType {
[Tasty] protected virtual void DoSomething() { }
}
internal class DerivedType : BaseType {
protected override void DoSomething() { }
}
Ushbu kodda DerivedType va uning DoSomething metodi ikkalasi ham Tasty deb hisoblanadi, chunki TastyAttribute klassi meros qilib olinadigan deb belgilangan. Biroq, DerivedType serializatsiyalanadigan emas, chunki FCL ning SerializableAttribute klassi meros qilib olinmaydigan atribut sifatida belgilangan.
.NET Framework faqat klasslar, metodlar, xususiyatlar, hodisalar, maydonlar, metod qaytarish qiymatlari va parametrlar maqsadlarini meros qilib olinadigan deb hisoblashini bilib qo'ying. Shuning uchun, atribut turini aniqlayotganingizda, maqsadlaringiz ushbu maqsadlarning istalganini o'z ichiga olgan holdagina Inherited ni true ga o'rnatishingiz kerak. E'tibor bering, meros qilib olingan atributlar hosil bo'lgan turlar uchun boshqariladigan modulga qo'shimcha metama'lumotlar yozilishiga olib kelmaydi. Men bu haqda biroz keyinroq "Custom Atribut Ishlatilishini Aniqlash" bo'limida batafsilroq gaplashaman.
Agar siz o'z atribut klassingizni aniqlasangiz va AttributeUsage atributini klassga qo'llashni unutsangiz, kompilyator va CLR atributingiz barcha maqsadlarga qo'llanilishi, faqat bir marta bitta maqsadga qo'llanilishi va meros qilib olinishi mumkin deb hisoblaydi. Bu taxminlar AttributeUsageAttribute klassidagi standart maydon qiymatlarini takrorlaydi.
Atribut Konstruktori va Maydon/Xususiyat Ma'lumot Turlari
O'z custom atribut klassingizni aniqlashda, dasturchilar atributning instansiyasini qo'llaganlarida ko'rsatishi kerak bo'lgan parametrlarni qabul qiluvchi konstruktorni aniqlashingiz mumkin. Bundan tashqari, turda dasturchi atribut klassining instansiyasi uchun ixtiyoriy ravishda tanlashi mumkin bo'lgan sozlamalarni belgilaydigan nostatik ommaviy maydonlar va xususiyatlarni aniqlashingiz mumkin.
Atribut klassining instansiya konstruktori, maydonlari va xususiyatlarini aniqlashda siz o'zingizni ma'lumot turlarining kichik kichik to'plamiga cheklashingiz kerak. Aniqrog'i, ruxsat berilgan ma'lumot turlari quyidagilardan iborat: Boolean, Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, String, Type, Object, yoki sanab o'tiladigan tur. Bundan tashqari, siz ushbu turlarning istalganining bitta o'lchamli, noldan boshlanadigan massividan foydalanishingiz mumkin. Biroq, massivlardan foydalanishdan qochishingiz kerak, chunki konstruktori massiv qabul qiladigan custom atribut klassi CLS ga mos emas.
Atributni qo'llashda, siz atribut klassi tomonidan aniqlangan turga mos keladigan kompilyatsiya vaqtidagi konstanta ifodani uzatishingiz kerak. Atribut klassi Type parametri, Type maydoni yoki Type xususiyatini aniqlagan joylarda, quyidagi kodda ko'rsatilganidek, C# ning typeof operatoridan foydalanishingiz kerak. Atribut klassi Object parametri, Object maydoni yoki Object xususiyatini aniqlagan joylarda, Int32, String yoki boshqa har qanday konstanta ifodani (null ni ham o'z ichiga olgan) uzatishingiz mumkin. Agar konstanta ifoda qiymat turini ifodalasa, atributning instansiyasi runtime da yaratilganda qiymat turi boxed (o'ralgan) bo'ladi.
Mana atribut va uning ishlatilishiga misol.
using System;
internal enum Color { Red }
[AttributeUsage(AttributeTargets.All)]
internal sealed class SomeAttribute : Attribute {
public SomeAttribute(String name, Object o, Type[] types) {
// 'name' String ga ishora qiladi
// 'o' qonuniy turlardan biriga ishora qiladi (kerak bo'lsa boxing)
// 'types' 1-o'lchamli, 0-asosli Type massiviga ishora qiladi
}
}
[Some("Jeff", Color.Red, new Type[] { typeof(Math), typeof(Console) })]
internal sealed class SomeType {
}
Mantiqiy jihatdan, kompilyator maqsadga qo'llanilgan custom atributni aniqlaganda, kompilyator atribut klassining konstruktorini belgilangan parametrlarni uzatib chaqirish orqali atribut klassining instansiyasini yaratadi. So'ngra kompilyator kengaytirilgan konstruktor sintaksisi orqali belgilangan qiymatlar yordamida istalgan ommaviy maydonlar va xususiyatlarni initsializatsiya qiladi. Endi custom atribut obyekti initsializatsiya qilingandan so'ng, kompilyator atribut obyektining holatini maqsadning metama'lumot jadvali yozuviga serializatsiya qiladi.
Men dasturchilarga custom atributlar haqida o'ylashning eng yaxshi usuli shunday deb topdim: metama'lumotlarda joylashgan bayt oqimiga serializatsiya qilingan klasslar instansiyalari. Keyinchalik, runtime da, klass instansiyasi metama'lumotdagi baytlarni deserializatsiya qilish orqali yaratilishi mumkin. Aslida, kompilyator atribut klassi instansiyasini yaratish uchun zarur bo'lgan ma'lumotlarni metama'lumotga yozadi. Har bir konstruktor parametri 1-baytli tur identifikatori, so'ngra qiymat bilan yoziladi. Konstruktor parametrlarini "serializatsiya" qilgandan keyin, kompilyator belgilangan maydon va xususiyat qiymatlarining har birini maydon/xususiyat nomi, so'ngra 1-baytli tur identifikatori, so'ngra qiymat yozish orqali yozadi. Massivlar uchun avval elementlar soni saqlanadi, so'ngra har bir alohida element.
Custom Atribut Ishlatilishini Aniqlash
Atribut klassini aniqlash o'zi foydali emas. Albatta, siz ularning instansiyalarini xohlagancha aniqlashingiz va qo'llashingiz mumkin, lekin bu faqat assembliyaga qo'shimcha metama'lumot yozilishiga olib keladi — ilova kodingizning xatti-harakati o'zgarmaydi.
15-bob "Sanab O'tiladigan Turlar va Bit Bayroqlari" da siz Flags atributini sanab o'tiladigan turga qo'llash System.Enum ning ToString va Format metodlari xatti-harakatini o'zgartirganini ko'rdingiz. Bu metodlar boshqacha ishlashining sababi shundaki, ular runtime da sanab o'tiladigan turda Flags atribut metama'lumoti bog'langan yoki yo'qligini tekshiradi. Kod atributlar mavjudligini reflection (aks ettirish) deb ataladigan texnologiya yordamida tekshirishi mumkin. Men bu yerda reflectionning qisqacha namoyishlarini beraman, lekin uni to'liq 23-bob "Assembly Yuklash va Reflection" da muhokama qilaman.
Agar siz Enum ning Format metodini amalga oshirish uchun mas'ul Microsoft xodimi bo'lsangiz, uni quyidagicha amalga oshirgan bo'lar edingiz.
public override String ToString() {
// Sanab o'tiladigan turda
// FlagsAttribute tur instansiyasi qo'llanilganmi?
if (this.GetType().IsDefined(typeof(FlagsAttribute), false)) {
// Ha; qiymatni bit bayroqlari sanab o'tish turi sifatida qarang.
...
} else {
// Yo'q; qiymatni oddiy sanab o'tish turi sifatida qarang.
...
}
}
Bu kod Type ning IsDefined metodini chaqirib, tizimdan sanab o'tiladigan tur uchun metama'lumotni ko'rib chiqishni va FlagsAttribute klassining instansiyasi u bilan bog'langanligini tekshirishni so'raydi. Agar IsDefined true qaytarsa, FlagsAttribute instansiyasi sanab o'tiladigan tur bilan bog'langan va Format metodi qiymatni bit bayroqlari to'plami sifatida ko'radi. Agar IsDefined false qaytarsa, Format qiymatni oddiy sanab o'tiladigan tur sifatida ko'radi.
Demak, agar siz o'z atribut klasslaringizni aniqlasangiz, siz ham atribut klassingiz instansiyasining mavjudligini (ba'zi maqsadda) tekshiradigan va keyin muqobil kod yo'lini bajaradigan ba'zi kodlarni yozishingiz kerak. Custom atributlarni aynan shu narsa juda foydali qiladi!
FCL atribut mavjudligini tekshirishning ko'plab usullarini taklif qiladi. Agar siz System.Type obyekti orqali atributning mavjudligini tekshirayotgan bo'lsangiz, oldinroq ko'rsatilganidek IsDefined metodidan foydalanishingiz mumkin. Biroq, ba'zan siz atributni turdan boshqa maqsadda, masalan, assembly, modul yoki metod kabi maqsadda tekshirishni xohlaysiz. Ushbu muhokama uchun System.Reflection.CustomAttributeExtensions klassi tomonidan aniqlangan kengaytma (extension) metodlariga e'tibor qaratamiz. Bu klass maqsad bilan bog'langan atributlarni olish uchun uchta statik metodni aniqlaydi: IsDefined, GetCustomAttributes va GetCustomAttribute. Bu funksiyalarning har biri bir nechta overloaded versiyalariga ega.
18-1 Jadval: CustomAttributeExtensions Metodlari
System.Reflection.CustomAttributeExtensions ning CLS-ga mos Custom Atributlar instansiyalarini qidirish uchun metama'lumotni tekshiradigan metodlari.
| Metod | Tavsif |
|---|---|
IsDefined |
Agar belgilangan Attribute-dan hosil bo'lgan klassning kamida bitta instansiyasi maqsad bilan bog'langan bo'lsa, true qaytaradi. Bu metod samarali, chunki u atribut klassining hech qanday instansiyasini yaratmaydi (deserializatsiya qilmaydi). |
GetCustomAttributes |
Maqsadga qo'llanilgan belgilangan atribut obyektlari to'plamini qaytaradi. Har bir instansiya kompilyatsiya paytida belgilangan parametrlar, maydonlar va xususiyatlar yordamida yaratiladi (deserializatsiya qilinadi). Agar maqsadda belgilangan atribut klassining instansiyalari bo'lmasa, bo'sh to'plam qaytariladi. Bu metod odatda AllowMultiple true ga o'rnatilgan atributlar bilan yoki barcha qo'llanilgan atributlarni ro'yxatlash uchun ishlatiladi. |
GetCustomAttribute |
Maqsadga qo'llanilgan belgilangan atribut klassining instansiyasini qaytaradi. Instansiya kompilyatsiya paytida belgilangan parametrlar, maydonlar va xususiyatlar yordamida yaratiladi (deserializatsiya qilinadi). Agar maqsadda belgilangan atribut klassining instansiyalari bo'lmasa, null qaytariladi. Agar maqsadda belgilangan atribut klassining bir nechta instansiyalari qo'llanilgan bo'lsa, System.Reflection.AmbiguousMatchException istisnosi tashlanadi. Bu metod odatda AllowMultiple false ga o'rnatilgan atributlar bilan ishlatiladi. |
Aniqlash Haqida Muhim Eslatmalar
Agar siz faqat atributning maqsadga qo'llanilganligini ko'rishni xohlasangiz, IsDefined ni chaqirishingiz kerak, chunki u boshqa ikki metoddan ko'ra samaraliroq. Biroq, atribut maqsadga qo'llanilganini bilganingizda, siz atributning konstruktori va ixtiyoriy ravishda o'rnatilgan maydon va xususiyatlariga parametrlarni belgilashingiz mumkin ekanligini bilasiz. IsDefined dan foydalanish atribut obyektini yaratmaydi, uning konstruktorini chaqirmaydi yoki uning maydon va xususiyatlarini o'rnatmaydi.
Agar atribut obyektini yaratishni xohlasangiz, GetCustomAttributes yoki GetCustomAttribute ni chaqirishingiz kerak. Har safar bu metodlarning biri chaqirilganda, u belgilangan atribut turining yangi instansiyalarini yaratadi va manba kodida belgilangan qiymatlar asosida instansiyaning har bir maydon va xususiyatini o'rnatadi. Bu metodlar to'liq yaratilgan atribut klasslarining instansiyalariga havolalar qaytaradi.
Bu metodlarning istalganini chaqirganingizda, ular ichki jihatdan boshqariladigan modulning metama'lumotlarini skanerlashi, belgilangan custom atribut klassini joylash uchun satr solishtirishlarini amalga oshirishi kerak. Shubhasiz, bu operatsiyalar vaqt oladi. Agar ishlash (performance) haqida o'ylayotgan bo'lsangiz, bir xil ma'lumotni qayta-qayta so'rash o'rniga bu metodlarni chaqirish natijasini keshlashni o'ylab ko'rishingiz kerak.
System.Reflection nomlar fazosi modul tarkibini tekshirish imkonini beruvchi bir nechta klasslarni aniqlaydi: Assembly, Module, ParameterInfo, MemberInfo, Type, MethodInfo, ConstructorInfo, FieldInfo, EventInfo, PropertyInfo va ularning tegishli *Builder klasslari. Bu klasslarning barchasi ham IsDefined va GetCustomAttributes metodlarini taklif qiladi.
Reflection klasslari tomonidan aniqlangan GetCustomAttributes versiyasi Attribute instansiyalari massivi (Attribute[]) o'rniga Object instansiyalari massivini (Object[]) qaytaradi. Bu reflection klasslari CLS ga mos bo'lmagan atribut klasslarining obyektlarini ham qaytarish imkoniyatiga ega bo'lganligi sababli. Biroq, bu nomuvofiqlik haqida tashvishlanmasligingiz kerak, chunki CLS ga mos bo'lmagan atributlar juda kam uchraydi. Aslida, men .NET Framework bilan ishlaganimning barcha davomida hech qachon birini ham ko'rmaganman.
Faqat Attribute, Type va MethodInfo klasslari Boolean inherit parametrini hisobga oladigan reflection metodlarini amalga oshirishini bilib qo'ying. Atributlarni qidiradigan boshqa barcha reflection metodlari inherit parametrini e'tiborsiz qoldiradi va meros ierarxiyasini tekshirmaydi. Agar hodisalar, xususiyatlar, konstruktorlar, maydonlar yoki parametrlar uchun meros qilib olingan atributning mavjudligini tekshirishingiz kerak bo'lsa, Attribute ning metodlaridan birini chaqirishingiz kerak.
Yana bir narsani bilishingiz kerak: IsDefined, GetCustomAttribute yoki GetCustomAttributes ga klass uzatganingizda, bu metodlar siz ko'rsatgan atribut klassining yoki undan hosil bo'lgan har qanday atribut klassining qo'llanilishini qidiradi. Agar kodingiz muayyan atribut klassini qidirayotgan bo'lsa, qidirilayotgan narsa aynan siz qidirayotgan klass ekanligini ta'minlash uchun qaytarilgan qiymatga qo'shimcha tekshirish o'tkazishingiz kerak. Potensial chalkashlikni kamaytirish va bu qo'shimcha tekshiruvni yo'q qilish uchun atribut klassingizni sealed deb aniqlashni ham o'ylab ko'rishingiz mumkin.
Namuna Kod: Tur va Metod Atributlarini Ko'rsatish
Mana tur ichida aniqlangan barcha metodlarni ro'yxatlaydigan va har bir metodga qo'llanilgan atributlarni ko'rsatadigan namuna kod. Kod namoyish maqsadida; odatda siz bu maqsadlarga custom atributlarni qo'llamaysiz.
using System;
using System.Diagnostics;
using System.Reflection;
[assembly: CLSCompliant(true)]
[Serializable]
[DefaultMemberAttribute("Main")]
[DebuggerDisplayAttribute("Richter", Name = "Jeff", Target = typeof(Program))]
public sealed class Program {
[Conditional("Debug")]
[Conditional("Release")]
public void DoSomething() { }
public Program() {
}
[CLSCompliant(true)]
[STAThread]
public static void Main() {
// Ushbu turga qo'llanilgan atributlar to'plamini ko'rsatish
ShowAttributes(typeof(Program));
// Tur bilan bog'langan metodlar to'plamini olish
var members =
from m in typeof(Program).GetTypeInfo().DeclaredMembers.OfType<MethodBase>()
where m.IsPublic
select m;
foreach (MemberInfo member in members) {
// Ushbu a'zoga qo'llanilgan atributlar to'plamini ko'rsatish
ShowAttributes(member);
}
}
private static void ShowAttributes(MemberInfo attributeTarget) {
var attributes = attributeTarget.GetCustomAttributes<Attribute>();
Console.WriteLine("Attributes applied to {0}: {1}",
attributeTarget.Name, (attributes.Count() == 0 ? "None" : String.Empty));
foreach (Attribute attribute in attributes) {
// Har bir qo'llanilgan atributning turini ko'rsatish
Console.WriteLine(" {0}", attribute.GetType().ToString());
if (attribute is DefaultMemberAttribute)
Console.WriteLine(" MemberName={0}",
((DefaultMemberAttribute) attribute).MemberName);
if (attribute is ConditionalAttribute)
Console.WriteLine(" ConditionString={0}",
((ConditionalAttribute) attribute).ConditionString);
if (attribute is CLSCompliantAttribute)
Console.WriteLine(" IsCompliant={0}",
((CLSCompliantAttribute) attribute).IsCompliant);
DebuggerDisplayAttribute dda = attribute as DebuggerDisplayAttribute;
if (dda != null) {
Console.WriteLine(" Value={0}, Name={1}, Target={2}",
dda.Value, dda.Name, dda.Target);
}
}
Console.WriteLine();
}
}
Ushbu ilovani kompilyatsiya qilish va ishga tushirish quyidagi natijani beradi.
Attributes applied to Program:
System.SerializableAttribute
System.Diagnostics.DebuggerDisplayAttribute
Value=Richter, Name=Jeff, Target=Program
System.Reflection.DefaultMemberAttribute
MemberName=Main
Attributes applied to DoSomething:
System.Diagnostics.ConditionalAttribute
ConditionString=Release
System.Diagnostics.ConditionalAttribute
ConditionString=Debug
Attributes applied to Main:
System.CLSCompliantAttribute
IsCompliant=True
System.STAThreadAttribute
Attributes applied to .ctor: None
Ikki Atribut Instansiyasini Bir-biriga Solishtirish
Endi kodingiz atribut instansiyasining maqsadga qo'llanilganligini qanday tekshirishni biladi, u atributning maydon qiymatlarini ko'rish uchun ularni tekshirishni xohlashi mumkin. Buni qilishning bir usuli bu atribut klassining maydonlari qiymatlarini aniq tekshiradigan kod yozishdir. Biroq, System.Attribute Object ning Equals metodini qayta aniqlaydi (override qiladi) va ichki jihatdan bu metod ikkala obyektning turlarini solishtiradi. Agar turlar bir xil bo'lmasa, Equals false qaytaradi. Agar turlar bir xil bo'lsa, Equals ikkala atribut obyektining maydonlari qiymatlarini solishtirish uchun reflectiondan foydalanadi (har bir maydon uchun Equals ni chaqiradi). Agar barcha maydonlar mos kelsa, true qaytariladi; aks holda false qaytariladi. Ishlashni yaxshilash uchun, reflectiondan foydalanishni yo'q qilish maqsadida o'z atribut klassingizda Equals ni qayta aniqlashingiz mumkin.
System.Attribute shuningdek boyroq semantikani ta'minlash uchun qayta aniqlashingiz mumkin bo'lgan virtual Match metodini ham taklif qiladi. Match ning standart amalga oshirilishi shunchaki Equals ni chaqiradi va uning natijasini qaytaradi. Quyidagi kod Equals va Match ni qanday qayta aniqlashni ko'rsatadi (Match bir atribut ikkinchisining kichik to'plami ekanligini tekshirganda true qaytaradi) va keyin Match qanday ishlatilishini ko'rsatadi.
Equals va Match Metodlarini Qayta Aniqlash
using System;
[Flags]
internal enum Accounts {
Savings = 0x0001,
Checking = 0x0002,
Brokerage = 0x0004
}
[AttributeUsage(AttributeTargets.Class)]
internal sealed class AccountsAttribute : Attribute {
private Accounts m_accounts;
public AccountsAttribute(Accounts accounts) {
m_accounts = accounts;
}
public override Boolean Match(Object obj) {
// Agar bazaviy klass Match ni amalga oshirsa va bazaviy klass
// Attribute bo'lmasa, quyidagi satrni izohdan chiqaring.
// if (!base.Match(obj)) return false;
// 'this' null bo'lmaganligi sababli, agar obj null bo'lsa,
// obyektlar mos bo'lmaydi
// ESLATMA: Agar siz bazaviy tur Match ni to'g'ri amalga oshirganiga
// ishonsangiz, bu satr o'chirilishi mumkin.
if (obj == null) return false;
// Agar obyektlar turli turlarga tegishli bo'lsa, ular mos bo'lmaydi
// ESLATMA: Agar siz bazaviy tur Match ni to'g'ri amalga oshirganiga
// ishonsangiz, bu satr o'chirilishi mumkin.
if (this.GetType() != obj.GetType()) return false;
// Maydonlarga murojaat qilish uchun obj ni bizning turga kasting qilish.
// ESLATMA: Bu kasting muvaffaqiyatsiz bo'lmaydi chunki biz
// obyektlar bir xil turda ekanligini bilamiz.
AccountsAttribute other = (AccountsAttribute) obj;
// Maydonlarni o'zingiz xohlagancha solishtiring
// Bu misol 'this' ning accounts boshqaning
// accounts kichik to'plami ekanligini tekshiradi
if ((other.m_accounts & m_accounts) != m_accounts)
return false;
return true; // Obyektlar mos
}
public override Boolean Equals(Object obj) {
// Agar bazaviy klass Equals ni amalga oshirsa va bazaviy klass
// Object bo'lmasa, quyidagi satrni izohdan chiqaring.
// if (!base.Equals(obj)) return false;
// 'this' null bo'lmaganligi sababli, agar obj null bo'lsa,
// obyektlar teng bo'lmaydi
// ESLATMA: Agar siz bazaviy tur Equals ni to'g'ri amalga oshirganiga
// ishonsangiz, bu satr o'chirilishi mumkin.
if (obj == null) return false;
// Agar obyektlar turli turlarga tegishli bo'lsa, ular teng bo'lmaydi
// ESLATMA: Agar siz bazaviy tur Equals ni to'g'ri amalga oshirganiga
// ishonsangiz, bu satr o'chirilishi mumkin.
if (this.GetType() != obj.GetType()) return false;
// Maydonlarga murojaat qilish uchun obj ni bizning turga kasting qilish.
// ESLATMA: Bu kasting muvaffaqiyatsiz bo'lmaydi chunki biz
// obyektlar bir xil turda ekanligini bilamiz.
AccountsAttribute other = (AccountsAttribute) obj;
// Maydonlarni solishtirish ular bir xil qiymatga ega ekanligini ko'rish uchun
// Bu misol 'this' ning accounts boshqaning accounts bilan
// bir xil ekanligini tekshiradi
if (other.m_accounts != m_accounts)
return false;
return true; // Obyektlar teng
}
// Equals ni qayta aniqlaganimiz uchun GetHashCode ni ham qayta aniqlash
public override Int32 GetHashCode() {
return (Int32) m_accounts;
}
}
Namuna: Match Yordamida Solishtirish
[Accounts(Accounts.Savings)]
internal sealed class ChildAccount { }
[Accounts(Accounts.Savings | Accounts.Checking | Accounts.Brokerage)]
internal sealed class AdultAccount { }
public sealed class Program {
public static void Main() {
CanWriteCheck(new ChildAccount());
CanWriteCheck(new AdultAccount());
// Bu faqat AccountsAttribute qo'llanilmagan turda ham
// metod to'g'ri ishlashini ko'rsatadi.
CanWriteCheck(new Program());
}
private static void CanWriteCheck(Object obj) {
// Atribut turining instansiyasini yaratish va uni
// biz aniq qidirayotgan narsaga initsializatsiya qilish.
Attribute checking = new AccountsAttribute(Accounts.Checking);
// Turga qo'llanilgan atribut instansiyasini yaratish
Attribute validAccounts =
obj.GetType().GetCustomAttribute<AccountsAttribute>(false);
// Agar atribut turga qo'llanilgan bo'lsa VA
// atribut "Checking" hisobini belgilagan bo'lsa, demak
// tur chek yozishi mumkin
if ((validAccounts != null) && checking.Match(validAccounts)) {
Console.WriteLine("{0} types can write checks.", obj.GetType());
} else {
Console.WriteLine("{0} types can NOT write checks.", obj.GetType());
}
}
}
Ushbu ilovani kompilyatsiya qilish va ishga tushirish quyidagi natijani beradi.
ChildAccount types can NOT write checks.
AdultAccount types can write checks.
Program types can NOT write checks.
Custom Atribut Ishlatilishini Attribute-dan Hosil Bo'lgan Obyektlarsiz Aniqlash
Ushbu bo'limda men metama'lumot yozuviga qo'llanilgan custom atributlarni aniqlashning muqobil usulini muhokama qilaman. Ba'zi xavfsizlik haqida o'ylaydigan stsenariylarda, bu muqobil usul Attribute-dan hosil bo'lgan klassda hech qanday kod bajarilmasligini ta'minlaydi. Axir, siz Attribute ning GetCustomAttribute(s) metodlarini chaqirganingizda, ular ichki jihatdan atribut klassining konstruktorini va xususiyat set aksessor metodlarini ham chaqirishi mumkin. Bundan tashqari, turga birinchi marta murojaat qilish CLR ning turning tip konstruktorini (agar mavjud bo'lsa) chaqirishiga olib keladi. Konstruktor, set aksessor va tip konstruktor metodlari kod o'z ichiga olishi mumkin bo'lib, bu kod faqat atributni qidirayotganda har safar bajariladi. Bu noma'lum kodning AppDomain da ishlashiga ruxsat beradi va bu potensial xavfsizlik zaifligidir.
Atribut klassi kodini bajarishga ruxsat bermasdan atributlarni aniqlash uchun, siz System.Reflection.CustomAttributeData klassidan foydalanasiz. Bu klass maqsad bilan bog'langan atributlarni olish uchun bitta statik metodni aniqlaydi: GetCustomAttributes. Bu metod to'rtta overloadga ega: biri Assembly, biri Module, biri ParameterInfo va biri MemberInfo qabul qiladi. Bu klass System.Reflection nomlar fazosida aniqlangan bo'lib, u 23-bobda muhokama qilinadi. Odatda siz CustomAttributeData klassini Assembly ning statik ReflectionOnlyLoad metodi orqali yuklangan assembly uchun metama'lumotlardagi atributlarni tahlil qilish uchun ishlatishingiz mumkin (bu ham 23-bobda muhokama qilinadi). Qisqacha aytganda, ReflectionOnlyLoad assembliyani CLR ning undagi har qanday kodni bajarishiga yo'l qo'ymaydigan tarzda yuklaydi; bu tip konstruktorlarni ham o'z ichiga oladi.
CustomAttributeData ning GetCustomAttributes metodi fabrika (factory) sifatida ishlaydi. Ya'ni, siz uni chaqirganingizda, u IList<CustomAttributeData> turidagi obyektda CustomAttributeData obyektlari to'plamini qaytaradi. To'plam belgilangan maqsadga qo'llanilgan har bir custom atribut uchun bitta element o'z ichiga oladi. Har bir CustomAttributeData obyekti uchun atribut obyekti qanday yaratilishi va initsializatsiya qilinishini aniqlash uchun ba'zi faqat-o'qish xususiyatlarini so'rashingiz mumkin. Aniqrog'i, Constructor xususiyati qaysi konstruktor metodi chaqirilishini ko'rsatadi, ConstructorArguments xususiyati bu konstruktorga uzatiladigan argumentlarni IList<CustomAttributeTypedArgument> instansiyasi sifatida qaytaradi, va NamedArguments xususiyati o'rnatiladigan maydonlar/xususiyatlarni IList<CustomAttributeNamedArgument> instansiyasi sifatida qaytaradi. Men oldingi gaplarda "bo'ladi" (would be) deb aytganimga e'tibor bering, chunki konstruktor va set aksessor metodlari aslida chaqirilmaydi — biz konstruktor va set aksessor metodlari bajarilishiga yo'l qo'ymaslik orqali qo'shimcha xavfsizlikka ega bo'lamiz.
CustomAttributeData Bilan Ishlash
Mana turli maqsadlarga qo'llanilgan atributlarni xavfsiz tarzda olish uchun CustomAttributeData klassidan foydalanadigan oldingi kod namunasining o'zgartirilgan versiyasi.
using System;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
[assembly: CLSCompliant(true)]
[Serializable]
[DefaultMemberAttribute("Main")]
[DebuggerDisplayAttribute("Richter", Name="Jeff", Target=typeof(Program))]
public sealed class Program {
[Conditional("Debug")]
[Conditional("Release")]
public void DoSomething() { }
public Program() {
}
[CLSCompliant(true)]
[STAThread]
public static void Main() {
// Ushbu turga qo'llanilgan atributlar to'plamini ko'rsatish
ShowAttributes(typeof(Program));
// Tur bilan bog'langan metodlar to'plamini olish
MemberInfo[] members = typeof(Program).FindMembers(
MemberTypes.Constructor | MemberTypes.Method,
BindingFlags.DeclaredOnly | BindingFlags.Instance |
BindingFlags.Public | BindingFlags.Static,
Type.FilterName, "*");
foreach (MemberInfo member in members) {
// Ushbu a'zoga qo'llanilgan atributlar to'plamini ko'rsatish
ShowAttributes(member);
}
}
private static void ShowAttributes(MemberInfo attributeTarget) {
IList<CustomAttributeData> attributes =
CustomAttributeData.GetCustomAttributes(attributeTarget);
Console.WriteLine("Attributes applied to {0}: {1}",
attributeTarget.Name, (attributes.Count == 0 ? "None" : String.Empty));
foreach (CustomAttributeData attribute in attributes) {
// Har bir qo'llanilgan atributning turini ko'rsatish
Type t = attribute.Constructor.DeclaringType;
Console.WriteLine(" {0}", t.ToString());
Console.WriteLine(" Constructor called={0}", attribute.Constructor);
IList<CustomAttributeTypedArgument> posArgs = attribute.ConstructorArguments;
Console.WriteLine(" Positional arguments passed to constructor:" +
((posArgs.Count == 0) ? " None" : String.Empty));
foreach (CustomAttributeTypedArgument pa in posArgs) {
Console.WriteLine(" Type={0}, Value={1}", pa.ArgumentType, pa.Value);
}
IList<CustomAttributeNamedArgument> namedArgs = attribute.NamedArguments;
Console.WriteLine(" Named arguments set after construction:" +
((namedArgs.Count == 0) ? " None" : String.Empty));
foreach(CustomAttributeNamedArgument na in namedArgs) {
Console.WriteLine(" Name={0}, Type={1}, Value={2}",
na.MemberInfo.Name, na.TypedValue.ArgumentType, na.TypedValue.Value);
}
Console.WriteLine();
}
Console.WriteLine();
}
}
Natija
Ushbu ilovani kompilyatsiya qilish va ishga tushirish quyidagi natijani beradi.
Attributes applied to Program:
System.SerializableAttribute
Constructor called=Void .ctor()
Positional arguments passed to constructor: None
Named arguments set after construction: None
System.Diagnostics.DebuggerDisplayAttribute
Constructor called=Void .ctor(System.String)
Positional arguments passed to constructor:
Type=System.String, Value=Richter
Named arguments set after construction:
Name=Name, Type=System.String, Value=Jeff
Name=Target, Type=System.Type, Value=Program
System.Reflection.DefaultMemberAttribute
Constructor called=Void .ctor(System.String)
Positional arguments passed to constructor:
Type=System.String, Value=Main
Named arguments set after construction: None
Attributes applied to DoSomething:
System.Diagnostics.ConditionalAttribute
Constructor called=Void .ctor(System.String)
Positional arguments passed to constructor:
Type=System.String, Value=Release
Named arguments set after construction: None
System.Diagnostics.ConditionalAttribute
Constructor called=Void .ctor(System.String)
Positional arguments passed to constructor:
Type=System.String, Value=Debug
Named arguments set after construction: None
Attributes applied to Main:
System.CLSCompliantAttribute
Constructor called=Void .ctor(Boolean)
Positional arguments passed to constructor:
Type=System.Boolean, Value=True
Named arguments set after construction: None
System.STAThreadAttribute
Constructor called=Void .ctor()
Positional arguments passed to constructor: None
Named arguments set after construction: None
Attributes applied to .ctor: None
Shartli Atribut Klasslari
Vaqt o'tishi bilan, atributlarni aniqlash, qo'llash va ular ustida reflection o'tkazishning qulayligi dasturchilarni ulardan tobora ko'proq foydalanishga undadi. Atributlardan foydalanish shuningdek bir vaqtning o'zida boy xususiyatlarni amalga oshirishda kodingizni izoh qilishning juda oson usuli hamdir. So'nggi vaqtlarda, dasturchilar dizayn vaqtida va nosozliklarni aniqlashda yordam berish uchun atributlardan foydalanmoqdalar. Masalan, Microsoft Visual Studio kod tahlili vositasi (FxCopCmd.exe) muayyan statik tahlil vositasi qoidasining buzilishi haqida xabar berishni bostirish uchun turlarga va a'zolarga qo'llashingiz mumkin bo'lgan System.Diagnostics.CodeAnalysis.SuppressMessageAttribute ni taklif qiladi. Bu atributni faqat kod tahlili vositasi qidiradi; dastur normal ishlayotganda atribut hech qachon izlanmaydi. Kod tahlilini ishlatmayotganingizda, metama'lumotlarda turgan SuppressMessage atributlari faqat metama'lumotni shishiradi, bu esa faylingizni kattalashtiradi, jarayoningizning ish to'plamini (working set) oshiradi va ilovangizning ishlashiga salbiy ta'sir qiladi. Agar kompilyatorni SuppressMessage atributlarini faqat kod tahlili vositasini ishlatishni mo'ljallaganingizda yozishga majburlashning oson usuli bo'lsa, yaxshi bo'lar edi. Yaxshiyamki, buning usuli bor: shartli atribut klasslaridan foydalanish.
Unga System.Diagnostics.ConditionalAttribute qo'llanilgan atribut klassi shartli atribut klassi deb ataladi. Mana misol.
//#define TEST
#define VERIFY
using System;
using System.Diagnostics;
[Conditional("TEST")][Conditional("VERIFY")]
public sealed class CondAttribute : Attribute {
}
[Cond]
public sealed class Program {
public static void Main() {
Console.WriteLine("CondAttribute is {0}applied to Program type.",
Attribute.IsDefined(typeof(Program),
typeof(CondAttribute)) ? "" : "not ");
}
}
Kompilyator CondAttribute ning instansiyasi maqsadga qo'llanilayotganini ko'rganda, kompilyator atribut ma'lumotlarini metama'lumotga faqat maqsadni o'z ichiga olgan kod kompilyatsiya qilinayotganda TEST yoki VERIFY belgisi aniqlangan bo'lsagina yozadi. Biroq, atribut klassining ta'rif metama'lumoti va amalga oshirilishi hali ham assembliyada mavjud.