10-Bob: Xususiyatlar (Properties)
Parametrsiz va parametrli xususiyatlar, avtomatik xususiyatlar (AIP), obyekt va to'plam initsializatorlari, anonim turlar, System.Tuple turi
Ushbu bobda xususiyatlar (properties) haqida gaplashamiz. Xususiyatlar manba kodga metodni soddalashtirilgan sintaksis orqali chaqirish imkonini beradi. Common language runtime (CLR) ikki xil xususiyatni taqdim etadi: parametrsiz xususiyatlar (parameterless properties) — ular oddiygina "xususiyatlar" deb ataladi, va parametrli xususiyatlar (parameterful properties) — ular turli dasturlash tillarida turlicha nomlanadi. Masalan, C# parametrli xususiyatlarni indexerlar deb ataydi, Visual Basic esa ularni default properties deb ataydi.
Shuningdek, ushbu bobda biz obyekt va to'plam initsializatorlari yordamida xususiyatlarni ishga tushirish haqida, bir qancha xususiyatlarni birga guruhlashning qo'shimcha usullari sifatida C# ning anonim turlari va System.Tuple turi haqida ham gaplashamiz.
Parametrsiz Xususiyatlar (Parameterless Properties)
Ko'pgina turlar olinishi yoki o'zgartirilishi mumkin bo'lgan holat ma'lumotlarini aniqlaydi. Ko'pincha bu holat ma'lumoti turning maydon a'zolari sifatida amalga oshiriladi. Masalan, quyida ikkita maydonga ega bo'lgan tur ta'rifi berilgan:
public sealed class Employee {
public String Name; // Xodimning ismi
public Int32 Age; // Xodimning yoshi
}
Agar siz ushbu turning nusxasini yaratsangiz, ushbu holat ma'lumotlarining istalganini quyidagi kodga o'xshash tarzda osongina olishingiz yoki o'rnatishingiz mumkin:
Employee e = new Employee();
e.Name = "Jeffrey Richter"; // Xodimning ismini o'rnatish
e.Age = 48; // Xodimning yoshini o'rnatish
Console.WriteLine(e.Name); // "Jeffrey Richter" ko'rsatadi
Ma'lumotlarni Inkapsulyatsiya Qilish
Obyektning holat ma'lumotlarini yuqorida ko'rsatilgan tarzda so'rash va o'rnatish juda keng tarqalgan. Biroq, men yuqoridagi kod hech qachon ko'rsatilgandek amalga oshirilmasligi kerak deb ta'kidlayman. Obyektga yo'naltirilgan loyihalash va dasturlashning asosiy tamoyillaridan biri ma'lumotlarni inkapsulyatsiya qilish (data encapsulation) hisoblanadi. Ma'lumotlarni inkapsulyatsiya qilish turningizning maydonlari hech qachon ommaviy tarzda ochilmasligi kerakligini anglatadi, chunki maydonlardan noto'g'ri foydalanadigan, obyektning holatini buzadigan kod yozish juda oson. Masalan, dasturchi quyidagi kod bilan Employee obyektini osongina buzishi mumkin:
e.Age = -5; // Kimningdir yoshi -5 bo'lishi mumkinmi?
Turning ma'lumot maydoniga kirish imkonini inkapsulyatsiya qilishning qo'shimcha sabablari ham bor. Masalan, siz maydonga kirishda biron-bir yon ta'sir (side effect) bajarilishini, biron-bir qiymatni keshlashni yoki ichki obyektni dangasa (lazy) yaratishni xohlashingiz mumkin. Yoki thread-safe bo'lishini xohlashingiz mumkin. Yoki maydon aslida xotirada baytlar bilan ifodalanmagan, balki biron-bir algoritm yordamida hisoblanadigan mantiqiy maydon bo'lishi mumkin.
Ushbu sabablardan istalganiga ko'ra, turni loyihalashda barcha maydonlaringizni private qilishni kuchli tavsiya etaman. Keyin, turningiz foydalanuvchisiga holat ma'lumotlarini olish yoki o'rnatish imkonini berish uchun siz metodlarni taqdim etasiz. Maydonga kirishni o'rab oladigan metodlar odatda accessor metodlar deb ataladi. Bu accessor metodlar ixtiyoriy ravishda to'g'rilik tekshiruvini amalga oshirishi va obyektning holati hech qachon buzilmasligini ta'minlashi mumkin. Masalan, men oldingi klassni quyidagicha qayta yozardim:
public sealed class Employee {
private String m_Name; // Maydon endi private
private Int32 m_Age; // Maydon endi private
public String GetName() {
return(m_Name);
}
public void SetName(String value) {
m_Name = value;
}
public Int32 GetAge() {
return(m_Age);
}
public void SetAge(Int32 value) {
if (value < 0)
throw new ArgumentOutOfRangeException("value", value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
Bu oddiy misol bo'lsa ham, siz ma'lumot maydonlarini inkapsulyatsiya qilishdan oladigan ulkan foyda ko'rishingiz mumkin. Siz faqat o'qish mumkin bo'lgan (read-only) yoki faqat yozish mumkin bo'lgan (write-only) xususiyatlarni yaratish qanchalik oson ekanligini ham ko'rishingiz kerak: accessor metodlardan birini amalga oshirmang, xolos. Shuningdek, siz SetXxx metodini protected deb belgilab, faqat hosila turlarga qiymatni o'zgartirishga ruxsat berishingiz mumkin.
Ma'lumotlarni yuqorida ko'rsatilgandek inkapsulyatsiya qilishning ikkita kamchiligi bor. Birinchidan, siz ko'proq kod yozishingiz kerak, chunki endi qo'shimcha metodlarni amalga oshirishingiz kerak. Ikkinchidan, turning foydalanuvchilari endi bitta maydon nomiga murojaat qilish o'rniga metodlarni chaqirishlari kerak:
e.SetName("Jeffrey Richter"); // Xodimning ismini yangilash
String EmployeeName = e.GetName(); // Xodimning ismini olish
e.SetAge(48); // Xodimning yoshini yangilash
e.SetAge(-5); // ArgumentOutOfRangeException chiqaradi
Int32 EmployeeAge = e.GetAge(); // Xodimning yoshini olish
Shaxsan men bu kamchiliklarni juda kichik deb hisoblayman. Shunga qaramay, dasturlash tillari va CLR xususiyatlar (properties) deb nomlangan mexanizmni taklif qiladi, bu birinchi kamchilikni biroz yengillashtiadi va ikkinchi kamchilikni butunlay yo'q qiladi.
C# da Xususiyatlar Sintaksisi
Quyida ko'rsatilgan klass xususiyatlardan foydalanadi va avvalroq ko'rsatilgan klassga funksional jihatdan teng:
public sealed class Employee {
private String m_Name;
private Int32 m_Age;
public String Name {
get { return(m_Name); }
set { m_Name = value; } // 'value' kalit so'zi doimo yangi qiymatni ifodalaydi.
}
public Int32 Age {
get { return(m_Age); }
set {
if (value < 0) // 'value' kalit so'zi doimo yangi qiymatni ifodalaydi.
throw new ArgumentOutOfRangeException("value", value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
}
Ko'rib turganingizdek, xususiyatlar turning ta'rifini biroz murakkablashtiradi, lekin ular sizga kodni quyidagicha yozish imkonini beradi, bu qo'shimcha ishga arziydi:
e.Name = "Jeffrey Richter"; // Xodimning ismini "o'rnatish"
String EmployeeName = e.Name;// Xodimning ismini "olish"
e.Age = 48; // Xodimning yoshini "o'rnatish"
e.Age = -5; // ArgumentOutOfRangeException chiqaradi
Int32 EmployeeAge = e.Age; // Xodimning yoshini "olish"
Xususiyatlarni aqlli maydonlar (smart fields) deb tasavvur qilishingiz mumkin: ularda qo'shimcha mantiq mavjud bo'lgan maydonlar. CLR static, instance, abstract va virtual xususiyatlarni qo'llab-quvvatlaydi. Bundan tashqari, xususiyatlar istalgan kirish darajasi modifikatori (accessibility modifier) bilan belgilanishi mumkin (6-bob, "Tur va A'zo Asoslari"da muhokama qilingan) va interfeys ichida aniqlanishi mumkin (13-bob, "Interfeyslar"da muhokama qilinadi).
Har bir xususiyatning nomi va turi bor (tur void bo'lishi mumkin emas). Xususiyatlarni turlarini farqlash (overload) mumkin emas (ya'ni bir xil nomli, lekin turlari farqli bo'lgan ikkita xususiyatga ega bo'lish mumkin emas). Xususiyatni aniqlashda siz odatda get va set accessor metodlarini belgilaysiz. Biroq, siz faqat o'qish mumkin bo'lgan (read-only) xususiyatni aniqlash uchun set metodini tashlab ketishingiz yoki faqat yozish mumkin bo'lgan (write-only) xususiyatni aniqlash uchun get metodini tashlab ketishingiz mumkin.
Xususiyatning get/set metodlari turda aniqlangan private maydonni boshqarishi odatiy holdir. Bu maydon odatda backing field (orqa maydon) deb ataladi. Lekin get va set metodlari backing maydonga kirishlari shart emas. Masalan, System.Threading.Thread turi operatsion tizim bilan to'g'ridan-to'g'ri aloqa o'rnatadigan Priority xususiyatini taklif qiladi; Thread obyekti oqimning ustunligi uchun maydon saqlamaydi. Backing maydonsiz xususiyatlarning boshqa misoli runtime da hisoblanadigan faqat o'qish mumkin bo'lgan xususiyatlardir — masalan, nol bilan tugaydigan massivning uzunligi yoki to'rtburchakning balandligi va kengligi ma'lum bo'lganda uning yuzasi.
Kompilyator Xususiyatlar Uchun Nima Hosil Qiladi
Siz xususiyatni aniqlasangiz, uning ta'rifiga qarab, kompilyator natijadagi boshqariladigan assembliyaga quyidagi ikki yoki uchta elementdan birini chiqaradi:
- Xususiyatning
getaccessor metodini ifodalovchi metod. Bu faqat xususiyat uchungetaccessor metodi aniqlangan bo'lsagina chiqariladi. - Xususiyatning
setaccessor metodini ifodalovchi metod. Bu faqat xususiyat uchunsetaccessor metodi aniqlangan bo'lsagina chiqariladi. - Boshqariladigan assembliyaning metama'lumotlarida xususiyat ta'rifi. Bu doimo chiqariladi.
Kompilyator bu metodlar uchun nomlarni xususiyat nomi oldiga get_ yoki set_ qo'shib avtomatik ravishda hosil qiladi.
C# xususiyatlarni o'rnatilgan tarzda qo'llab-quvvatlaydi. C# kompilyator xususiyatni olishga yoki o'rnatishga harakat qilayotgan kodni ko'rganida, kompilyator aslida ushbu metodlardan birini chaqirishni chiqaradi. Agar siz xususiyatlarni to'g'ridan-to'g'ri qo'llab-quvvatlamaydigan dasturlash tilidan foydalanayotgan bo'lsangiz, siz kerakli accessor metodini chaqirish orqali xususiyatlarga kirishingiz mumkin. Ta'sir aynan bir xil; shunchaki manba kodi unchalik chiroyli ko'rinmaydi.
Accessor metodlarini chiqarishdan tashqari, kompilyatorlar manba kodida aniqlangan har bir xususiyat uchun boshqariladigan assembliyaning metama'lumotlariga xususiyat ta'rifi yozuvini ham chiqaradi. Bu yozuv ba'zi bayroqlar va xususiyatning turini o'z ichiga oladi, hamda get va set accessor metodlariga ishora qiladi. Bu ma'lumot shunchaki "xususiyat" ning mavhum tushunchasi va uning accessor metodlari o'rtasida bog'lanish o'rnatish uchun mavjud. Kompilyatorlar va boshqa vositalar bu metama'lumotdan foydalanishi mumkin, va bu ma'lumotni System.Reflection.PropertyInfo klassidan foydalanib ham olish mumkin. Biroq, CLR ning o'zi bu metama'lumotni ishlatmaydi va ish vaqtida faqat accessor metodlarini talab qiladi.
Oldingi Employee turiga qaytib, kompilyator Name va Age xususiyatlarini uchrataganida, ikkala xususiyatda ham get va set accessor metodlari borligi sababli, kompilyator Employee turiga to'rtta metod ta'rifini chiqaradi. Xuddi asl manba kodi quyidagicha yozilgandek:
public sealed class Employee {
private String m_Name;
private Int32 m_Age;
public String get_Name(){
return m_Name;
}
public void set_Name(String value) {
m_Name = value; // 'value' argumenti doimo yangi qiymatni ifodalaydi.
}
public Int32 get_Age() {
return m_Age;
}
public void set_Age(Int32 value) {
if (value < 0) // 'value' doimo yangi qiymatni ifodalaydi.
throw new ArgumentOutOfRangeException("value", value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
Kompilyator avtomatik ravishda metod nomlarini xususiyat nomi oldiga get_ yoki set_ qo'shib hosil qiladi. C# xususiyatlarni o'rnatilgan tarzda qo'llab-quvvatlaydi: kompilyator xususiyatga yozish yoki o'qish kodini ko'rganida, u aslida shu accessor metodlaridan birini chaqirishni chiqaradi.
Avtomatik Ravishda Amalga Oshiriladigan Xususiyatlar (AIP)
Agar siz shunchaki backing maydoni inkapsulyatsiya qilish uchun xususiyat yaratayotgan bo'lsangiz, C# avtomatik ravishda amalga oshiriladigan xususiyatlar (Automatically Implemented Properties, AIP) deb atalgan soddalashtirilgan sintaksisni taklif qiladi. Mana bu yerda Name xususiyati uchun ko'rsatilganidek:
public sealed class Employee {
// Bu xususiyat avtomatik ravishda amalga oshirilgan xususiyatdir
public String Name { get; set; }
private Int32 m_Age;
public Int32 Age {
get { return(m_Age); }
set {
if (value < 0) // 'value' kalit so'zi doimo yangi qiymatni ifodalaydi.
throw new ArgumentOutOfRangeException("value", value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
}
Siz xususiyatni e'lon qilib, get/set metodlari uchun amalga oshirishni taqdim etmasangiz, C# kompilyatori avtomatik ravishda siz uchun private maydon e'lon qiladi. Bu misolda, maydon xususiyatning turi bo'lgan String turida bo'ladi. Va kompilyator maydondagi qiymatni qaytarish va maydon qiymatini o'rnatish uchun get_Name va set_Name metodlarini avtomatik ravishda amalga oshiradi.
Buni shunchaki ommaviy (public) String maydon e'lon qilishga nisbatan qanday farqi borligini so'rashingiz mumkin. Katta farq bor. AIP sintaksisidan foydalanish sizda xususiyat yaratilganligini anglatadi. Ushbu xususiyatga kiradigan har qanday kod aslida get va set metodlarini chaqiradi. Agar keyinchalik kompilyatorning standart amalga oshirishini qabul qilish o'rniga accessor metodlardan birini o'zingiz amalga oshirishga qaror qilsangiz, ushbu xususiyatga kiradigan har qanday kodni qayta kompilyatsiya qilish shart bo'lmaydi. Biroq, agar siz Nameni maydon sifatida e'lon qilgan bo'lsangiz va keyin uni xususiyatga o'zgartirsangiz, unda maydonga kirgan barcha kod xususiyat metodlariga kirish uchun qayta kompilyatsiya qilinishi kerak bo'lar edi.
AIP Ogohlantirishlari va Cheklovlari
Shaxsan men kompilyatorning AIP xususiyatini yoqtirmayman va uni odatda ishlatishdan qochaman. Buning bir nechta sabablari bor:
- Boshlang'ich qiymat berish mumkin emas: Maydon e'lon qilish sintaksisi e'lon qilish va boshlang'ich qiymat berishni bitta satrda amalga oshirish imkonini beradi. Biroq, AIPga boshlang'ich qiymat berish uchun qulay sintaksis yo'q. Shuning uchun, har bir konstruktor metodida har bir AIPni aniq initsializatsiya qilishingiz kerak.
- Serializatsiya muammosi: Runtime serializatsiya dvigatellari serializatsiya qilingan oqimda maydonning nomini saqlaydi. AIP uchun backing maydonning nomi kompilyator tomonidan aniqlanadi va u har safar kodni qayta kompilyatsiya qilganingizda o'zgarishi mumkin — bu AIPni o'z ichiga olgan turlarning nusxalarini deserializatsiya qilish qobiliyatini yo'qqa chiqaradi. Serializatsiya yoki deserializatsiya qilmoqchi bo'lgan turlarda AIP xususiyatidan foydalanmang.
- Debugging qiyinligi: Siz AIPning
getyokisetmetodiga breakpoint qo'ya olmaysiz, shuning uchun dastur xususiyatni olayotgan yoki o'rnatayotganini osongina aniqlay olmaysiz. Qo'lda amalga oshirilgan xususiyatlarga breakpointlar qo'yishingiz mumkin, bu xatolarni kuzatishda juda foydali bo'lishi mumkin.
Siz AIPlardan foydalanganingizda, xususiyat o'qiladigan va yoziladigan bo'lishi kerakligini ham bilishingiz kerak; ya'ni kompilyator get va set metodlarini ikkalasini ham hosil qilishi kerak. Bu mantiqan to'g'ri, chunki faqat yozish mumkin bo'lgan maydon o'z qiymatini o'qish qobiliyatisiz foydali emas; xuddi shunday, faqat o'qish mumkin bo'lgan maydon doimo standart qiymatiga ega bo'lar edi. Bundan tashqari, kompilyator tomonidan yaratilgan backing maydonning nomini bilmayotganingiz uchun, kodingiz doimo xususiyat nomi orqali xususiyatga kirishishi kerak. Va agar siz accessor metodlardan birini o'zingiz amalga oshirishga qaror qilsangiz, ikkala accessor metodini ham o'zingiz amalga oshirishingiz kerak va siz endi AIP xususiyatidan foydalanmayotgan bo'lasiz. Bitta xususiyat uchun AIP xususiyati — hammasi yoki hech narsa prinsipi.
AIP xususiyati faqat get va set ikkalasini ham ta'minlashni talab qiladi. Agar siz biron-bir accessor metodini o'zingiz amalga oshirmoqchi bo'lsangiz, ikkalasini ham o'zingiz amalga oshirishingiz va AIP ishlatmasligingiz kerak. AIP — hammasi yoki hech narsa.
Xususiyatlarni Aqlli Aniqlash
Shaxsan men xususiyatlarni yoqtirmayman va ular Microsoft .NET Framework va uning dasturlash tillarida qo'llab-quvvatlanmasligini xohlardim. Buning sababi shundaki, xususiyatlar maydonlarga o'xshaydi, lekin aslida ular metodlardir. Bu dasturchilar orasida juda ko'p chalkashlikka sabab bo'lishi ma'lum. Dasturchi maydonga kirayotgandek ko'rinadigan kodni ko'rganida, u xususiyat uchun to'g'ri bo'lmasligi mumkin bo'lgan ko'plab taxminlar qiladi. Masalan:
- Xususiyat faqat o'qish yoki faqat yozish mumkin bo'lishi mumkin; maydonga kirish doimo o'qiladigan va yoziladigan. Agar siz xususiyatni aniqlasangiz, eng yaxshisi
getvasetaccessor metodlarini ikkalasini ham taklif qilish. - Xususiyat metodi istisno (exception) chiqarishi mumkin; maydonga kirish hech qachon istisno chiqarmaydi.
- Xususiyat
outyokirefparametr sifatida metodga uzatilishi mumkin emas; maydon esa mumkin. Masalan, quyidagi kod kompilyatsiya qilinmaydi:
using System;
public sealed class SomeType {
private static String Name {
get { return null; }
set {}
}
static void MethodWithOutParam(out String n) { n = null; }
public static void Main() {
// Quyidagi satr uchun C# kompilyatori quyidagi xatoni chiqaradi:
// error CS0206: A property, indexer or dynamic member access may not
// be passed as an out or ref parameter
MethodWithOutParam(out Name);
}
}
- Xususiyat metodi uzoq vaqt bajarilishi mumkin; maydonga kirish doimo darhol tugaydi. Xususiyatlardan foydalanishning keng tarqalgan sababi oqimlarni sinxronlashdir (thread synchronization), bu oqimni abadiy to'xtatib qo'yishi mumkin, shuning uchun oqim sinxronlash kerak bo'lsa xususiyat ishlatilmasligi kerak. Bunday holatda metod afzaldir. Shuningdek, agar klassingiz masofadan kirish mumkin bo'lsa (masalan,
System.MarshalByRefObjectdan hosil bo'lgan), xususiyat metodini chaqirish juda sekin bo'ladi, shuning uchun metod xususiyatdan afzaldir. - Ketma-ket bir necha marta chaqirilsa, xususiyat metodi har safar boshqa qiymat qaytarishi mumkin; maydon har safar bir xil qiymatni qaytaradi.
System.DateTimeklassining faqat o'qish uchun mo'ljallanganNowxususiyati joriy sana va vaqtni qaytaradi. Har safar bu xususiyatni so'raganingizda, u boshqa qiymat qaytaradi. Bu xato bo'lib, MicrosoftNowni xususiyat o'rniga metod qilish orqali klassni tuzatishi mumkin edi deb xohlaydi.EnvironmentningTickCountxususiyati bu xatoning yana bir namunasi. - Xususiyat metodi kuzatiladigan yon ta'sirlarga (side effects) sabab bo'lishi mumkin; maydonga kirish hech qachon bunday qilmaydi. Boshqacha qilib aytganda, turning foydalanuvchisi tur tomonidan aniqlangan turli xususiyatlarni istalgan tartibda o'rnatishi kerak va boshqacha xatti-harakatni sezmasligi kerak.
- Xususiyat metodi qo'shimcha xotira talab qilishi yoki asl obyektning bir qismi bo'lmagan obyektga havolani qaytarishi mumkin, shuning uchun qaytarilgan obyektni o'zgartirish asl obyektga ta'sir qilmaydi; maydonga so'rov yuborish doimo asl obyektning holatining bir qismi bo'lgan obyektga havolani qaytaradi. Nusxasini qaytaradigan xususiyat bilan ishlash dasturchilar uchun juda chalg'ituvchi bo'lishi mumkin va bu xususiyat ko'pincha hujjatlashtirilmagan.
Mening e'tiborimga xususiyatlarni keragidan ko'ra tez-tez ishlatishayotgani keldi. Agar siz xususiyatlar va maydonlar o'rtasidagi farqlar ro'yxatini ko'rib chiqsangiz, xususiyatni aniqlash haqiqatan ham foydali bo'lgan va dasturchilar uchun chalkashlik tug'dirmaydigan juda kam holatlar borligini ko'rasiz. Xususiyatlar sizga beradigan yagona narsa biroz soddalashtirilgan sintaksisdir; xususiyat bo'lmagan metodni chaqirishga nisbatan hech qanday unumdorlik afzalligi yo'q, va kodning tushunarliligi kamayadi.
Agar men .NET Framework va kompilyatorlar loyihalashida qatnashganimda, xususiyatlarni umuman taklif qilmasdim; buning o'rniga dasturchilardan GetXxx va SetXxx metodlarini amalga oshirishlarini xohlardim. Keyin, kompilyatorlar ushbu metodlarni chaqirish uchun biron-bir maxsus, soddalashtirilgan sintaksisni taklif qilmoqchi bo'lishsa, shunday bo'lsin. Lekin men kompilyatordan maydon kirish sintaksisidan farqli sintaksisdan foydalanishini xohlardim, shunda dasturchilar aslida nima qilayotganlarini — metod chaqiruvini — haqiqatan ham tushunishsin.
Xususiyatlar va Visual Studio Debugger
Microsoft Visual Studio sizga debugger ning kuzatuv oynasida (watch window) obyektning xususiyatini kiritish imkonini beradi. Buni qilganingizda, har safar breakpointga yetganingizda, debugger xususiyatning get accessor metodini chaqiradi va qaytarilgan qiymatni ko'rsatadi. Bu xatolarni kuzatishda juda foydali bo'lishi mumkin, lekin u xatolarni ham keltirib chiqarishi va debugging unumdorligingizga zarar yetkazishi mumkin.
Masalan, siz tarmoq resursi bo'yicha fayl uchun FileStream yaratdingiz va keyin FileStreamning Length xususiyatini debugger ning kuzatuv oynasiga qo'shdingiz deylik. Endi har safar breakpointga yetganingizda, debugger Lengthning get accessor metodini chaqiradi, bu ichki jihatdan faylning joriy uzunligini olish uchun serverga tarmoq so'rovini yuboradi!
Xuddi shunday, agar xususiyatingizning get accessor metodida yon ta'sir (side effect) bo'lsa, bu yon ta'sir har safar breakpointga yetganingizda bajariladi. Masalan, xususiyatingizning get accessor metodi har safar chaqirilganida hisoblagichni oshirsin; bu hisoblagich endi har safar breakpointga yetganingizda oshiriladi.
Ushbu muammolar tufayli Visual Studio xususiyatlarni kuzatuv oynalarida baholashni (property evaluation) o'chirishga imkon beradi. Buni o'chirish uchun Visual Studio da Tools, Options, Debugging va General bo'limiga o'ting va ro'yxat panelida Enable Property Evaluation And Other Implicit Function Calls belgilash katakchasini olib tashlang.
Obyekt va To'plam Initsializatorlari
Obyektni yaratish va keyin uning ba'zi ommaviy xususiyatlarini (yoki maydonlarini) o'rnatish juda keng tarqalgan. Ushbu keng tarqalgan dasturlash naqshini soddalashtirish uchun C# tili maxsus obyekt initsializatsiya sintaksisini qo'llab-quvvatlaydi.
Obyekt Initsializatorlari (Object Initializers)
Quyidagi misol obyekt initsializatori sintaksisini ko'rsatadi:
Employee e = new Employee() { Name = "Jeff", Age = 45 };
Ushbu bitta ifoda bilan men Employee obyektini yaratyapman, uning parametrsiz konstruktorini chaqiryapman, va keyin uning ommaviy Name xususiyatini "Jeff" ga va ommaviy Age xususiyatini 45 ga o'rnatyapman. Aslida, oldingi kod quyidagiga aynan teng, buni Intermediate Language (IL) ni tekshirish orqali tasdiqlashingiz mumkin:
Employee _tempVar = new Employee();
_tempVar.Name = "Jeff";
_tempVar.Age = 45;
// Faqat yuqoridagi tayinlashlar istisno chiqarmasa e ga tayinlanadi.
// Bu e ni qisman initsializatsiya qilingan obyektga murojaat qilishdan saqlaydi.
Employee e = _tempVar;
Obyekt initsializatori sintaksisining haqiqiy foydasi shundaki, u sizga kodni ifoda kontekstida (expression context) yozishga imkon beradi (ifoda kontekstiga nisbatan operator konteksti), bu funksiyalarning kompozitsiyasini amalga oshirishga imkon beradi, bu o'z navbatida kod o'qilishini oshiradi. Masalan, men endi quyidagicha yozishim mumkin:
String s = new Employee() { Name = "Jeff", Age = 45 }.ToString().ToUpper();
Shunday qilib, bitta ifoda ichida men Employee obyektini yaratdim, uning konstruktorini chaqirdim, ikkita ommaviy xususiyatni initsializatsiya qildim va keyin natijada hosil bo'lgan ifoda ustida ToString ni, keyin ToUpper ni chaqirdim.
Kichik eslatma sifatida, C# parametrsiz konstruktorni chaqirmoqchi bo'lganingizda ochilgan qavsdan oldin qavslarni tashlab ketishga ham imkon beradi. Quyidagi satr oldingi satr bilan bir xil IL ni hosil qiladi:
String s = new Employee { Name = "Jeff", Age = 45 }.ToString().ToUpper();
To'plam Initsializatorlari (Collection Initializers)
Agar xususiyatning turi IEnumerable yoki IEnumerable<T> interfeysini amalga oshirsa, xususiyat to'plam (collection) deb hisoblanadi va to'plamni initsializatsiya qilish almashtirish operatsiyasi emas, balki qo'shish operatsiyasidir. Masalan, quyidagi klass ta'rifim bor deylik:
public sealed class Classroom {
private List<String> m_students = new List<String>();
public List<String> Students { get { return m_students; } }
public Classroom() {}
}
Endi men Classroom obyektini yaratadigan va Students to'plamini initsializatsiya qiladigan kodga ega bo'lishim mumkin:
public static void M() {
Classroom classroom = new Classroom {
Students = { "Jeff", "Kristin", "Aidan", "Grant" }
};
// Sinfdagi 4 talabani ko'rsatish
foreach (var student in classroom.Students)
Console.WriteLine(student);
}
Ushbu kodni kompilyatsiya qilganida, kompilyator Students xususiyati List<String> turida ekanligini va bu turda IEnumerable<String> interfeysi amalga oshirilganligini ko'radi. Endi kompilyator List<String> turida Add metodi mavjudligini taxmin qiladi (chunki ko'pgina to'plam klasslari elementlarni qo'shadigan Add metodini taklif qiladi). Keyin kompilyator to'plamning Add metodini chaqiradigan kodni hosil qiladi. Shunday qilib, oldingi kod kompilyator tomonidan quyidagiga aylantiriladi:
public static void M() {
Classroom classroom = new Classroom();
classroom.Students.Add("Jeff");
classroom.Students.Add("Kristin");
classroom.Students.Add("Aidan");
classroom.Students.Add("Grant");
// Sinfdagi 4 talabani ko'rsatish
foreach (var student in classroom.Students)
Console.WriteLine(student);
}
Agar xususiyatning turi IEnumerable yoki IEnumerable<T> ni amalga oshirsa, lekin tur Add metodini taklif qilmasa, kompilyator to'plam initsializator sintaksisidan foydalanib elementlar qo'shishga ruxsat bermaydi; buning o'rniga kompilyator quyidagiga o'xshash xabarni chiqaradi: error CS0117: 'System.Collections.Generic.IEnumerable<string>' does not contain a definition for 'Add'.
Ba'zi to'plamlarning Add metodlari bir nechta argumentni qabul qiladi — masalan, Dictionaryning Add metodi:
public void Add(TKey key, TValue value);
Siz to'plam initsializatorida ichma-ich qavslarni ishlatib Add metodiga bir nechta argumentni o'tkazishingiz mumkin:
var table = new Dictionary<String, Int32> {
{ "Jeffrey", 1 }, { "Kristin", 2 }, { "Aidan", 3 }, { "Grant", 4 }
};
Oldingi satr quyidagiga teng:
var table = new Dictionary<String, Int32>();
table.Add("Jeffrey", 1);
table.Add("Kristin", 2);
table.Add("Aidan", 3);
table.Add("Grant", 4);
Anonim Turlar (Anonymous Types)
C# ning anonim tur xususiyati juda oddiy va ixcham sintaksis yordamida o'zgarmas tuple turini avtomatik ravishda e'lon qilishga imkon beradi. Tuple — bu odatda bir-biri bilan qandaydir tarzda bog'liq bo'lgan xususiyatlar to'plamini o'z ichiga olgan turdir. Quyidagi kodning yuqori satrida men ikkita xususiyatga ega (Name String turida, va Year Int32 turida) klassni aniqlayapman, ushbu turning nusxasini yaratyapman va Name xususiyatini "Jeff" ga, Year xususiyatini 1964 ga o'rnatyapman:
// Turni aniqlash, uning nusxasini yaratish va xususiyatlarini initsializatsiya qilish
var o1 = new { Name = "Jeff", Year = 1964 };
// Xususiyatlarni konsolda ko'rsatish:
Console.WriteLine("Name={0}, Year={1}", o1.Name, o1.Year);// Ko'rsatadi: Name=Jeff, Year=1964
Ushbu kodning yuqori satri anonim turni yaratadi, chunki men new kalit so'zidan keyin tur nomini ko'rsatmadim, shuning uchun kompilyator men uchun avtomatik ravishda tur nomini yaratadi va menga nima ekanligini aytmaydi (shu sababli u anonim tur deb ataladi). Ushbu kod satri oldingi bo'limda muhokama qilingan obyekt initsializator sintaksisidan xususiyatlarni e'lon qilish va ularni initsializatsiya qilish uchun foydalanadi. Shuningdek, men (ishlab chiquvchi sifatida) kompilyatsiya vaqtida turning nomini bilmaganligim uchun, o'zgaruvchini qanday turda e'lon qilishni bilmayman. Biroq, bu muammo emas, chunki men C# ning to'g'ridan-to'g'ri tiplangan (implicitly typed) lokal o'zgaruvchi xususiyatidan (var) foydalanib, kompilyatorga tayinlash operatorining o'ng tomonidagi ifodadan turni chiqarishga imkon beraman.
Kompilyator Anonim Turlar Uchun Nima Yaratadi
Endi kompilyator aslida nima qilayotganiga to'xtalib o'taylik. Siz quyidagi kod satrini yozganingizda:
var o = new { property1 = expression1, ..., propertyN = expressionN };
kompilyator har bir ifodaning turini chiqaradi, har bir maydon uchun private maydonlar yaratadi, har bir maydon uchun ommaviy faqat o'qish mumkin bo'lgan xususiyatlarni yaratadi va barcha ifodalarni qabul qiladigan konstruktorni yaratadi. Konstruktor kodi private faqat o'qish mumkin bo'lgan maydonlarni ifoda natijalari qiymatlaridan initsializatsiya qiladi. Bundan tashqari, kompilyator Object ning Equals, GetHashCode va ToString metodlarini qayta yozadi (override) va barcha metodlar ichida kod hosil qiladi. Aslida, kompilyator hosil qilgan klass quyidagiga o'xshaydi:
[CompilerGenerated]
internal sealed class <>f__AnonymousType0<...>: Object {
private readonly t1 f1;
public t1 p1 { get { return f1; } }
private readonly tn fn;
public tn pn { get { return fn; } }
public <>f__AnonymousType0<...>(t1 a1, ..., tn an) {
f1 = a1; ...; fn = an; // Barcha maydonlarni o'rnatish
}
public override Boolean Equals(Object value) {
// Agar biron-bir maydon mos kelmasa false qaytaradi; aks holda true
}
public override Int32 GetHashCode() {
// Har bir maydonning hash kodidan hash kod qaytaradi
}
public override String ToString() {
// Vergul bilan ajratilgan xususiyat nomi = qiymat juftliklarini qaytaradi
}
}
Kompilyator Equals va GetHashCode metodlarini hosil qiladi, shunda anonim turning nusxalari xesh jadval to'plamiga joylashtirilishi mumkin. Xususiyatlar o'qish/yozish mumkin bo'lganiga nisbatan readonly sifatida yaratiladi, bu obyektning xeshkodining o'zgarishiga to'sqinlik qiladi. Xesh jadvalda kalit sifatida ishlatilgan obyektning xeshkodini o'zgartirish obyektni topib bo'lmasligiga olib kelishi mumkin. Kompilyator ToString metodini debugging bilan yordam berish uchun hosil qiladi.
Kompilyator xususiyat nomlari va turlarini o'zgaruvchilardan chiqarish uchun ikkita qo'shimcha sintaksisni qo'llab-quvvatlaydi:
String Name = "Grant";
DateTime dt = DateTime.Now;
// Ikki xususiyatli anonim tur
// 1. String Name xususiyati Grant ga o'rnatilgan
// 2. Int32 Year xususiyati dt ichidagi yilga o'rnatilgan
var o2 = new { Name, dt.Year };
Ushbu misolda kompilyator birinchi xususiyat Name deb nomlanishi kerakligini aniqlaydi. Name lokal o'zgaruvchining nomi bo'lgani uchun, kompilyator xususiyatning turini lokal o'zgaruvchi bilan bir xil turga, ya'ni Stringga o'rnatadi. Ikkinchi xususiyat uchun kompilyator maydon/xususiyat nomini ishlatadi: Year. Year DateTime klassining Int32 turdagi xususiyati bo'lib, shuning uchun anonim turdagi Year xususiyati ham Int32 bo'ladi.
Anonim Turlar va Tur Identikligi
Kompilyator anonim turlarni aniqlash haqida juda aqlli. Agar kompilyator manba kodingizda bir xil tuzilishga ega bir nechta anonim turlarni aniqlayotganingizni ko'rsa, kompilyator anonim tur uchun faqat bitta ta'rif yaratadi va ushbu turning bir nechta nusxasini yaratadi. "Bir xil tuzilish" deganda men anonim turlarning har bir xususiyat uchun bir xil tur va nomga ega bo'lishini va bu xususiyatlar bir xil tartibda ko'rsatilganligini nazarda tutyapman.
Ikki o'zgaruvchi bir xil turga ega bo'lgani uchun, biz teng qiymatlarni tekshirish va bir obyektga havolani boshqasining o'zgaruvchisiga tayinlash kabi ajoyib ishlarni qilishimiz mumkin:
// Bitta tur tenglik va tayinlash operatsiyalarini amalga oshirish imkonini beradi.
Console.WriteLine("Objects are equal: " + o1.Equals(o2));
o1 = o2; // Tayinlash
Shuningdek, ushbu tur identikligi tufayli biz anonim turlarning to'g'ridan-to'g'ri tiplangan massivini (implicitly typed array) yaratishimiz mumkin (16-bob, "Massivlar"dagi "Massiv Elementlarini Initsializatsiya Qilish" bo'limida muhokama qilingan):
// Bu ishlaydi chunki barcha obyektlar bir xil anonim turda
var people = new[] {
o1, // Oldingi bo'limdan
new { Name = "Kristin", Year = 1970 },
new { Name = "Aidan", Year = 2003 },
new { Name = "Grant", Year = 2008 }
};
// Bu anonim turlar massivi bo'ylab yurishni ko'rsatadi (var talab qilinadi)
foreach (var person in people)
Console.WriteLine("Person={0}, Year={1}", person.Name, person.Year);
Anonim turlar ko'pincha Language Integrated Query (LINQ) texnologiyasi bilan birga ishlatiladi, ya'ni siz bitta anonim turda bo'lgan obyektlar to'plamiga olib keladigan so'rovni bajarganda. Keyin siz natijadagi to'plamdagi obyektlarni xuddi shu metod ichida qayta ishlaysiz. Mana bir misol — so'nggi yetti kun ichida o'zgartirilgan Mening Hujjatlarim papkamizdagi barcha fayllarni qaytaradigan:
String myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var query =
from pathname in Directory.GetFiles(myDocuments)
let LastWriteTime = File.GetLastWriteTime(pathname)
where LastWriteTime > (DateTime.Now - TimeSpan.FromDays(7))
orderby LastWriteTime
select new { Path = pathname, LastWriteTime };// Anonim tur obyektlar to'plami
foreach (var file in query)
Console.WriteLine("LastWriteTime={0}, Path={1}", file.LastWriteTime, file.Path);
Anonim turlar nusxalari metod tashqarisiga "chiqishi" mumkin emas. Metod anonim turni parametr sifatida qabul qiladigan prototip bilan aniqlana olmaydi, chunki anonim turni ko'rsatish imkoni yo'q. Xuddi shunday, metod anonim turga havolani qaytarishini ko'rsata olmaydi. Agar siz tuple ni uzatmoqchi bo'lsangiz, keyingi bo'limda muhokama qilinadigan System.Tuple turidan foydalanishingiz kerak.
System.Tuple Turi
System nom fazosida Microsoft bir nechta generik Tuple turlarini aniqlagan (barchasi Objectdan hosil bo'lgan), ular argumentlar soni (arity) bo'yicha farqlanadi. Eng oddiy va eng murakkabi quyidagicha ko'rinadi:
// Bu eng oddiysi:
[Serializable]
public class Tuple<T1> {
private T1 m_Item1;
public Tuple(T1 item1) { m_Item1 = item1; }
public T1 Item1 { get { return m_Item1; } }
}
// Bu eng murakkabi:
[Serializable]
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> {
private T1 m_Item1; private T2 m_Item2; private T3 m_Item3; private T4 m_Item4;
private T5 m_Item5; private T6 m_Item6; private T7 m_Item7; private TRest m_Rest;
public Tuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7,
TRest rest) {
m_Item1 = item1; m_Item2 = item2; m_Item3 = item3; m_Item4 = item4;
m_Item5 = item5; m_Item6 = item6; m_Item7 = item7; m_Rest = rest;
}
public T1 Item1 { get { return m_Item1; } }
public T2 Item2 { get { return m_Item2; } }
public T3 Item3 { get { return m_Item3; } }
public T4 Item4 { get { return m_Item4; } }
public T5 Item5 { get { return m_Item5; } }
public T6 Item6 { get { return m_Item6; } }
public T7 Item7 { get { return m_Item7; } }
public TRest Rest { get { return m_Rest; } }
}
Anonim turlar kabi, Tuple yaratilgandan keyin u o'zgarmasdir (barcha xususiyatlar faqat o'qish mumkin). Buni bu yerda ko'rsatmasam ham, Tuple klasslari CompareTo, Equals, GetHashCode va ToString metodlarini, shuningdek Size xususiyatini ham taklif qiladi. Bundan tashqari, barcha Tuple turlari IStructuralEquatable, IStructuralComparable va IComparable interfeyslarini amalga oshiradi, shunda siz ikki Tuple obyektini bir-biri bilan taqqoslab, ularning maydonlari qanday solishtirilishini ko'rishingiz mumkin.
Tuple Ishlatish Misollari
Mana bir metod misoli, chaqiruvchiga ikkita ma'lumotni qaytarish uchun Tuple turidan foydalanadigan:
// Item1 da minimal, Item2 da maksimal qaytaradi
private static Tuple<Int32, Int32> MinMax(Int32 a, Int32 b) {
return new Tuple<Int32, Int32>(Math.Min(a, b), Math.Max(a, b));
}
// Bu metodni chaqirish va qaytarilgan Tuple ni ishlatish
private static void TupleTypes() {
var minmax = MinMax(6, 2);
Console.WriteLine("Min={0}, Max={1}", minmax.Item1, minmax.Item2); // Min=2, Max=6
}
Albatta, Tuplening ishlab chiqaruvchisi va iste'molchisi Item# xususiyatlarida nima qaytarilayotganini aniq tushunishi juda muhim. Anonim turlar bilan xususiyatlar manba kodidagi anonim turni aniqlash asosida haqiqiy nomlarga ega bo'ladi. Tuple turlarida xususiyatlarga Microsoft tomonidan belgilangan Item# nomlari beriladi va siz buni o'zgartira olmaysiz. Afsuski, bu nomlar hech qanday haqiqiy ma'noga ega emas, shuning uchun ishlab chiqaruvchi va iste'molchiga ma'no berish uchun izohlar (comments) qo'shishingiz kerak. Bu shuningdek, kod o'qilishini va saqlanishini kamaytiradi.
Kompilyator generik turlarni faqat generik metodni chaqirayotganda chiqarishi mumkin, konstruktorni chaqirayotganda emas. Shu sababli, System nom fazosi generik turlarni argumentlardan chiqaradigan statik Create metodlariga ega bo'lgan generik bo'lmagan, statik Tuple klassini ham o'z ichiga oladi. Bu klass Tuple obyektlarini yaratish uchun zavod (factory) sifatida ishlaydi va kodingizni soddalashtirish uchun mavjud. Yuqorida ko'rsatilgan MinMax metodini statik Tuple klassidan foydalanib qayta yozish:
// Item1 da minimal, Item2 da maksimal qaytaradi
private static Tuple<Int32, Int32> MinMax(Int32 a, Int32 b) {
return Tuple.Create(Math.Min(a, b), Math.Max(a, b)); // Soddaroq sintaksis
}
Agar siz sakkizdan ko'proq elementli Tuple yaratmoqchi bo'lsangiz, Rest parametri uchun boshqa Tuple ni uzatasiz:
var t = Tuple.Create(0, 1, 2, 3, 4, 5, 6, Tuple.Create(7, 8));
Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}",
t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, t.Item7,
t.Rest.Item1, t.Rest.Item1.Item1, t.Rest.Item1.Item2);
Anonim turlar va Tuple turlariga qo'shimcha ravishda, siz System.Dynamic.ExpandoObject klassiga (System.Core.dll assembliyasida aniqlangan) ham e'tibor qaratishingiz mumkin. Ushbu klassni C# ning dynamic turidan (5-bob, "Primitiv, Reference va Value Turlar"da muhokama qilingan) foydalanib ishlatsangiz, xususiyatlar to'plamini (kalit/qiymat juftliklari) birlashtirishning yana bir usuli bor. Natija kompilyatsiya vaqtida tur xavfsiz emas (garchi IntelliSense ni qo'llab-quvvatlamasangiz ham), lekin sintaksis chiroyli ko'rinadi va ExpandoObject obyektlarini C# va Python kabi dinamik tillar o'rtasida uzatishingiz mumkin. Mana ExpandoObjectdan foydalanadigan namuna kod:
dynamic e = new System.Dynamic.ExpandoObject();
e.x = 6; // Qiymati 6 bo'lgan Int32 'x' xususiyatini qo'shish
e.y = "Jeff"; // Qiymati "Jeff" bo'lgan String 'y' xususiyatini qo'shish
e.z = null; // Qiymati null bo'lgan Object 'z' xususiyatini qo'shish
// Barcha xususiyatlar va ularning qiymatlarini ko'rish:
foreach (var v in (IDictionary<String, Object>)e)
Console.WriteLine("Key={0}, V={1}", v.Key, v.Value);
// 'x' xususiyatini va uning qiymatini o'chirish
var d = (IDictionary<String, Object>)e;
d.Remove("x");
Parametrli Xususiyatlar (Indexerlar)
Oldingi bo'limda xususiyatlarning get accessor metodlari hech qanday parametr qabul qilmadi. Shu sababli men bu xususiyatlarni parametrsiz xususiyatlar deb atadim. Bu xususiyatlar maydonlarga kirishni his qildirish tufayli tushunish oson. Ushbu maydon kabi xususiyatlarga qo'shimcha ravishda, dasturlash tillari men parametrli xususiyatlar deb ataydigan narsani ham qo'llab-quvvatlaydi, ularning get accessor metodlari bitta yoki bir nechta parametrni, set accessor metodlari esa ikkita yoki undan ko'p parametrlarni qabul qiladi. Turli dasturlash tillari parametrli xususiyatlarni turli yo'llar bilan ochadi. Shuningdek, tillar parametrli xususiyatlarga murojaat qilish uchun turli atamalardan foydalanadi: C# ularni indexerlar, Visual Basic esa default properties deb ataydi.
C# da parametrli xususiyatlar (indekserlar) massivga o'xshash sintaksis yordamida ochiladi. Boshqacha aytganda, siz indekserni C# dasturchisi uchun [] operatorini yuklab olish (overload) usuli deb tasavvur qilishingiz mumkin. Mana BitArray klassining misoli, bu klass klassning saqlaydigan bitlar to'plamiga indeks orqali kirish uchun massivga o'xshash sintaksisdan foydalanishga imkon beradi:
BitArray Indekser Misoli
using System;
public sealed class BitArray {
// Bitlarni saqlaydigan baytlar massivi
private Byte[] m_byteArray;
private Int32 m_numBits;
// Baytlar massivini ajratadigan va barcha bitlarni 0 ga o'rnatadigan konstruktor
public BitArray(Int32 numBits) {
// Avval argumentlarni tekshirish.
if (numBits <= 0)
throw new ArgumentOutOfRangeException("numBits must be > 0");
// Bitlar sonini saqlash.
m_numBits = numBits;
// Bit massivi uchun baytlarni ajratish.
m_byteArray = new Byte[(numBits + 7) / 8];
}
// Bu indekser (parametrli xususiyat).
public Boolean this[Int32 bitPos] {
// Bu indekserning get accessor metodi.
get {
// Avval argumentlarni tekshirish
if ((bitPos < 0) || (bitPos >= m_numBits))
throw new ArgumentOutOfRangeException("bitPos");
// Indekslangan bitning holatini qaytarish.
return (m_byteArray[bitPos / 8] & (1 << (bitPos % 8))) != 0;
}
// Bu indekserning set accessor metodi.
set {
if ((bitPos < 0) || (bitPos >= m_numBits))
throw new ArgumentOutOfRangeException("bitPos", bitPos.ToString());
if (value) {
// Indekslangan bitni yoqish (on).
m_byteArray[bitPos / 8] = (Byte)
(m_byteArray[bitPos / 8] | (1 << (bitPos % 8)));
} else {
// Indekslangan bitni o'chirish (off).
m_byteArray[bitPos / 8] = (Byte)
(m_byteArray[bitPos / 8] & ~(1 << (bitPos % 8)));
}
}
}
}
BitArray klassining indekseridan foydalanish juda oddiy:
// 14 bitni sig'dira oladigan BitArray ajratish.
BitArray ba = new BitArray(14);
// Set accessor ni chaqirib barcha juft raqamli bitlarni yoqish.
for (Int32 x = 0; x < 14; x++) {
ba[x] = (x % 2 == 0);
}
// Get accessor ni chaqirib barcha bitlarning holatini ko'rsatish.
for (Int32 x = 0; x < 14; x++) {
Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off"));
}
BitArray misolida indekser bitta Int32 parametr, bitPos ni qabul qiladi. Barcha indekserlar kamida bitta parametrga ega bo'lishi kerak, lekin ular bir nechta bo'lishi ham mumkin. Bu parametrlar (shuningdek qaytarish turi) istalgan ma'lumot turida bo'lishi mumkin (void dan tashqari). Birdan ko'p parametrli indekserning misoli System.Drawing.Imaging.ColorMatrix klassida topilishi mumkin.
Assotsiativ massivda qiymatlarni qidirish uchun indekserdan foydalanish juda keng tarqalgan. Aslida, System.Collections.Generic.Dictionary turi kalit qabul qiladigan va kalit bilan bog'langan qiymatni qaytaradigan indekserni taklif qiladi. Parametrsiz xususiyatlardan farqli o'laroq, tur bir nechta yuklangan (overloaded) indekserlarga imxon beradi, ulaning imzolari (signature) farqli bo'lsa kifoya.
Parametrsiz xususiyatning set accessor metodi singari, indekserning set accessor metodida ham C# da value deb nomlangan yashirin parametr mavjud. Bu parametr "indekslangan element" uchun kerakli yangi qiymatni bildiradi.
Kompilyator Indekserlar Uchun Nima Hosil Qiladi
CLR parametrsiz xususiyatlar va parametrli xususiyatlarni farqlamaydi; CLR uchun har biri shunchaki tur ichida aniqlangan bir juft metod va metama'lumotning bir qismidan iborat. Oldinroq aytilganidek, C# ning indekserni ifodalash uchun this[...] sintaksisini talab qilishi to'liq C# jamoasi tomonidan qilingan tanlov edi. Bu tanlov nimani anglatadi degan savol C# indekserlarni faqat turlarning nusxalarida aniqlashga ruxsat beradi. C# dasturchiga statik indekser xususiyatini aniqlashga imkon beradigan sintaksisni taklif qilmaydi, garchi CLR statik parametrli xususiyatlarni qo'llab-quvvatlaydi.
Kompilyator bu metodlar uchun nomlarni avtomatik ravishda get_ va set_ ni indekser nomiga qo'shib hosil qiladi. C# sintaksisi indekser uchun dasturchiga indekser nomini ko'rsatishga ruxsat bermagani uchun, C# kompilyator jamoasi accessor metodlari uchun standart nomni tanlashi kerak edi; ular Item ni tanladi. Shuning uchun, kompilyator tomonidan hosil qilingan metod nomlari get_Item va set_Item bo'ladi.
Oldinroq ko'rsatilgan BitArray klassi uchun kompilyator indekserni xuddi asl manba kodi quyidagicha yozilgandek kompilyatsiya qiladi:
public sealed class BitArray {
// Bu indekserning get accessor metodi.
public Boolean get_Item(Int32 bitPos) { /* ... */ }
// Bu indekserning set accessor metodi.
public void set_Item(Int32 bitPos, Boolean value) { /* ... */ }
}
C# da siz indekserning standart Item nomini System.Runtime.CompilerServices.IndexerNameAttribute maxsus atributi yordamida o'zgartirishingiz mumkin. Quyidagi kod buni qanday qilishni ko'rsatadi:
using System;
using System.Runtime.CompilerServices;
public sealed class BitArray {
[IndexerName("Bit")]
public Boolean this[Int32 bitPos] {
// Kamida bitta accessor metodi bu yerda aniqlangan
}
}
Endi kompilyator get_Item va set_Item o'rniga get_Bit va set_Bit deb nomlangan metodlarni hosil qiladi. Kompilyatsiya paytida C# kompilyator IndexerName atributini ko'radi, va bu atribut kompilyatorga metodlar va xususiyat metama'lumotlarini qanday nomlashni aytadi; atributning o'zi assembliyaning metama'lumotlariga chiqarilmaydi.
Aytgancha, System.String turi indekserining nomini o'zgartirgan turga misoldir. String ning indekseri Item o'rniga Chars deb nomlangan. Bu faqat o'qish mumkin bo'lgan xususiyat sizga satr ichidagi alohida belgini olish imkonini beradi. [] operator sintaksisini ishlatmaydigan dasturlash tillari uchun Chars yanada mazmunliroq nom bo'lishi qaror qilindi.
C# da bitta tur bir nechta indekserlarni aniqlashi mumkin, ulaning barchasi turli parametr to'plamlarini qabul qilishi kerak. Boshqa dasturlash tillarida IndexerName atributi har biriga boshqacha nom berish imkonini beradi, chunki har birining turli nomi bo'lishi mumkin. C# bunga ruxsat bermaydi, chunki uning sintaksisi qaysi indekserga murojaat qilayotganingizni bilmasligi kerak — nomi bo'yicha emas, faqat parametr turi bo'yicha farqlanishi mumkin.
Agar turda parametrli xususiyat bo'lsa va siz IndexerName atributini ko'rsatmasangiz, ta'riflovchi turda Item ni ko'rsatuvchi DefaultMember atributi bo'ladi. Agar siz parametrli xususiyatga IndexerName atributini qo'llasangiz, ta'riflovchi turda IndexerName atributida ko'rsatilgan satr nomini ko'rsatuvchi DefaultMember atributi bo'ladi. Bir nechta parametrli xususiyatlarni qo'llab-quvvatlaydigan til uchun xususiyat metod nomlaridan biri turning DefaultMember atributi tomonidan tanlangan va aniqlangan bo'lishi kerak. Bu C# kirishi mumkin bo'lgan yagona parametrli xususiyatdir.
Xususiyat Accessor Metodlarini Chaqirish Unumdorligi
Oddiy get va set accessor metodlari uchun just-in-time (JIT) kompilyator kodni inline qiladi (to'g'ridan-to'g'ri chaqiruvchi metod ichiga joylashtiradi), shunda maydonlar o'rniga xususiyatlardan foydalanish natijasida hech qanday ish vaqti unumdorlik kamayishi bo'lmaydi. Inline qilish — bu metod (yoki accessor metod, bu holda) uchun kodning chaqiruvni amalga oshiradigan metodda to'g'ridan-to'g'ri kompilyatsiya qilinishi degan ma'noni anglatadi. Bu chaqiruvning qo'shimcha xarajatini olib tashlaydi, ish vaqtida tezlikni oshirish hisobiga kompilyatsiya qilingan metod kodini kattalashtiradi. Xususiyat accessor metodlari odatda juda oz koddan iborat bo'lgani uchun, ularni inline qilish native kodni kichiklashtirishi va tezroq bajarilishiga olib kelishi mumkin.
JIT kompilyatori debugging vaqtida xususiyat metodlarini inline qilmaydi, chunki inline qilingan kodni debug qilish qiyinroq. Bu shuni anglatadiki, xususiyatga kirish unumdorligi release buildda tez, debug buildda esa sekin bo'lishi mumkin. Maydonga kirish esa debug va release buildlarda ikkalasida ham tezdir.
Xususiyat Accessor Kirish Darajasi (Property Accessor Accessibility)
Ba'zan turni loyihalashda get accessor metodi uchun bitta kirish darajasi va set accessor metodi uchun boshqa kirish darajasiga ega bo'lish kerak bo'ladi. Eng keng tarqalgan senariy — ommaviy (public) get accessor va himoyalangan (protected) set accessor:
public class SomeType {
private String m_name;
public String Name {
get { return m_name; }
protected set { m_name = value; }
}
}
Yuqoridagi koddan ko'rinib turibdiki, Name xususiyati o'zi public xususiyat sifatida e'lon qilingan, va bu get accessor metodi ommaviy bo'lishini va shuning uchun barcha kod tomonidan chaqirilishi mumkinligini anglatadi. Biroq, set accessor protected deb e'lon qilingan va faqat SomeType ichida yoki SomeTypedan hosil bo'lgan klassdagi koddan chaqirilishi mumkin.
Turli kirish darajalariga ega accessor metodlari bo'lgan xususiyatni aniqlashda, C# sintaksisi xususiyatning o'zi eng kam cheklovchi kirish darajasi bilan e'lon qilinishini va yanada cheklovli kirish darajasi faqat accessor metodlaridan biriga qo'llanishini talab qiladi. Yuqoridagi misolda xususiyat public, set accessor esa protected (publicdan ko'ra cheklovliroq).
Generik Xususiyat Accessor Metodlari
Xususiyatlar aslida metodlar bo'lgani va C# hamda CLR metodlarni generik bo'lishga ruxsat bergani uchun, ba'zan odamlar o'z generik tur parametrlarini kiritadigan xususiyatlarni aniqlashni xohlaydilar (o'rab oluvchi turning generik tur parametridan foydalanishdan farqli o'laroq). Biroq, C# bunga ruxsat bermaydi. Xususiyatlar o'z generik tur parametrlarini kiritmaydi degan asosiy sabab shundaki, bu kontseptual jihatdan mantiqiy emas. Xususiyat so'rov qilinishi yoki o'rnatilishi mumkin bo'lgan obyektning xarakteristikasini ifodalashi kerak. Generik tur parametrini kiritish so'rov qilish/o'rnatish xatti-harakati o'zgarishi mumkinligini anglatadi, lekin kontseptual jihatdan xususiyat xatti-harakatga ega bo'lmasligi kerak. Agar ob'yektingiz biron-bir xatti-harakatni ochmoqchi bo'lsa — generik bo'lsin yoki bo'lmasin — metod aniqlang, xususiyat emas.
Ushbu bobda siz xususiyatlarning barcha jihatlarini o'rgandingiz:
- Parametrsiz xususiyatlar — maydonlarga o'xshash sintaksis bilan accessor metodlarini chaqiradigan qulay mexanizm. Ma'lumotlarni inkapsulyatsiya qilish va to'g'rilik tekshiruvini ta'minlash uchun ishlatiladi.
- Avtomatik xususiyatlar (AIP) — kompilyator backing maydon va oddiy get/set metodlarini avtomatik hosil qiladigan soddalashtirilgan sintaksis. Lekin cheklovlari bor: boshlang'ich qiymat berish, serializatsiya, debugging masalalari.
- Obyekt initsializatorlari — obyektni yaratish va xususiyatlarini bitta ifodada o'rnatish imkonini beradi.
- To'plam initsializatorlari —
IEnumerableni amalga oshirgan turlarningAddmetodini avtomatik chaqirish orqali to'plamlarni initsializatsiya qilish. - Anonim turlar — kompilyator tomonidan avtomatik yaratilgan o'zgarmas turlar; LINQ bilan keng ishlatiladi.
- System.Tuple — metod chegaralari bo'ylab bir nechta qiymatlarni uzatish uchun umumiy maqsadli o'zgarmas tuple turlari.
- Parametrli xususiyatlar (indexerlar) — C# da
this[...]sintaksisi orqali massivga o'xshash kirish imkonini ta'minlovchi xususiyatlar. - Unumdorlik — JIT kompilyator oddiy accessor metodlarni inline qiladi, shuning uchun maydonlarga nisbatan unumdorlik kamayishi bo'lmaydi.
- Accessor kirish darajasi — get va set accessor metodlari uchun turli kirish darajalari belgilanishi mumkin.
- Generik accessor — xususiyatlar o'z generik tur parametrlarini kirita olmaydi; buning o'rniga metod ishlatilishi kerak.