9-Bob: Parametrlar
Ixtiyoriy parametrlar, yashirin turlangan o'zgaruvchilar, havola orqali uzatish (ref/out), o'zgaruvchan sonli argumentlar (params) va parametr/qaytish turi yo'riqnomalari
Ushbu bobda metod parametrlari bilan bog'liq turli mexanizmlar haqida gaplashiladi. Xususan, metodlarga parametrlarning ixtiyoriy (optional) va nomlangan (named) sifatida qanday e'lon qilinishi, var kalit so'zi orqali mahalliy o'zgaruvchilar turini yashirin (implicitly) belgilash, parametrlarni havola orqali (by reference) uzatish va metodga o'zgaruvchan sonli argumentlarni uzatish haqida o'rganasiz.
Bobning oxirida esa metod parametrlari va qaytish turlari uchun eng yaxshi amaliyotlar (best practices) ko'rib chiqiladi.
Ixtiyoriy Parametrlar (Optional Parameters)
Metod e'lon qilayotganda ba'zi parametrlarga standart qiymat (default value) belgilash mumkin. Bu parametrlarni ixtiyoriy (optional) qiladi — chaqiruvchi kod bu parametrlar uchun argument berishi shart emas. Agar argument berilmasa, kompilyator standart qiymatni ishlatadi.
Quyidagi misolda s parametri majburiy, x parametri standart qiymat 9 ga, s2 esa "A" ga ega:
private static Int32 s_n = 0;
private static void M(Int32 x = 9, String s = "A",
DateTime dt = default(DateTime), Guid guid = new Guid()) {
Console.WriteLine("x={0}, s={1}, dt={2}, guid={3}", x, s, dt, guid);
}
public static void Main() {
// 1. Barcha argumentlarni oshkor (explicitly) ko'rsatish
M(8, "X", DateTime.Now, Guid.NewGuid());
// 2. Barcha ixtiyoriy parametrlar uchun standart qiymatlar ishlatiladi
M();
// 3. Faqat birinchi parametr uchun argument berilgan
M(5);
}
Dastur chaqirilganda, ixtiyoriy parametrlar uchun standart qiymatlar foydalaniladi. Masalan, M() chaqirilganda x 9 bo'ladi, s esa "A" bo'ladi.
Ixtiyoriy parametrlar uchun standart qiymatlar kompilyatsiya vaqtida konstantalar bo'lishi kerak. Ya'ni, primitiv turlar uchun kompilyatsiya vaqtidagi konstantalar (Int32, String, va h.k.) yoki default(T) va new ifodasi value turlar uchun ishlatilishi mumkin. Reference turlar uchun standart qiymat faqat null bo'lishi mumkin.
Ixtiyoriy Parametrlarning Qoidalari va Cheklovlari
Ixtiyoriy parametrlar bilan ishlashda bir qator qoidalarga amal qilish zarur:
- Standart qiymatga ega parametrlar (ixtiyoriy parametrlar) majburiy parametrlardan keyin kelishi kerak. Ya'ni, parametrlar ro'yxatida avval standart qiymati bo'lmagan parametrlar, keyin standart qiymatli parametrlar joylashtiriladi. Bitta istisno bor:
paramsmassivi doimo parametrlar ro'yxatining eng oxirida bo'lishi kerak va unga standart qiymat berish mumkin emas. - Standart qiymatlar kompilyatsiya vaqtidagi konstantalar bo'lishi kerak (primitiv turlar, enumlar,
nullqabul qiluvchi har qanday reference tur). Value turlar uchundefault(T)yokinew ValType()(bu ikkalasi bir xil natija beradi) ishlatilishi mumkin. - Ixtiyoriy parametrlarning standart qiymatlarini o'zgartirmang, agar metod assembly chegarasidan tashqarida chaqirilsa. Chunki chaqiruvchi kod standart qiymatlarni o'z IL kodiga joylashtiradi va qayta kompilyatsiya qilinguniga qadar eski standart qiymatlardan foydalanadi.
refyokioutparametrlarga standart qiymat berib bo'lmaydi, chunki ular uchun o'zgaruvchini uzatishning imkoni yo'q.
Ixtiyoriy parametrlarning standart qiymatlari chaqiruvchi tomonidagi IL kodga joylashtiriladi — huddi konstantalar kabi. Agar siz metod e'lonidagi standart qiymatni o'zgartirsangiz, lekin chaqiruvchi kodni qayta kompilyatsiya qilmasangiz, chaqiruvchi kod eski standart qiymatdan foydalanishda davom etadi. Shuning uchun ixtiyoriy parametrlardan ehtiyotkorlik bilan foydalaning!
Standart Qiymatlar Haqida Batafsil
C# kompilyatori ixtiyoriy parametrlarni qo'llab-quvvatlash uchun CLR ning OptionalAttribute va DefaultParameterValueAttribute atributlaridan foydalanadi. Keling, quyidagi misol bilan kompilyator qanday ishlashini ko'rib chiqamiz:
// C# dagi e'lon
private static void M(Int32 x = 9, String s = "A") {
// ...
}
// Kompilyator aslida quyidagi metadata ni hosil qiladi:
// [Optional][DefaultParameterValue(9)] Int32 x
// [Optional][DefaultParameterValue("A")] String s
C# kompilyatori chaqiruvchi kodda argument berilmagan parametrlarni ko'rganda, metadata dan standart qiymatni oladi va uni chaqiruv joyiga (call site) joylashtiradi. Bu shuni anglatadiki, standart qiymat chaqiruvchining IL kodiga to'g'ridan-to'g'ri kiritiladi.
Agar sizda overload qilingan metodlar va ixtiyoriy parametrlar o'rtasida tanlash imkoniyati bo'lsa, ixtiyoriy parametrlar bilan bir metod yozish ko'pincha qulay, ammo ehtiyot bo'ling — versiyalash muammosini yodda tuting. Agar metod boshqa assembliyalar tomonidan chaqirilsa va standart qiymat kelajakda o'zgarishi mumkin bo'lsa, overload metodlardan foydalanish xavfsizroq.
Nomlangan Argumentlar (Named Arguments)
Metodga argumentlarni uzatishda odatda ularni parametrlar e'lon qilingan tartibda berish kerak. C# nomlangan argumentlar (named arguments) yordamida argumentlarni istalgan tartibda berish imkonini beradi — parametr nomini ko'rsatib, keyin vergul bilan qiymatni berish orqali.
private static void M(Int32 x, DateTime dt, String s) {
// ...
}
public static void Main() {
// Oddiy chaqiruv — argumentlar tartibda
M(5, DateTime.Now, "Hello");
// Nomlangan argumentlar — istalgan tartibda
M(s: "Hello", x: 5, dt: DateTime.Now);
// Aralashtirish — avval pozitsion, keyin nomlangan
M(5, s: "Hello", dt: DateTime.Now);
}
Nomlangan argumentlar ayniqsa ixtiyoriy parametrlar bilan birgalikda ishlatilganda juda foydali bo'ladi. Masalan, quyidagi metodni ko'ring:
private static void M(Int32 x = 9, String s = "A",
DateTime dt = default(DateTime), Guid guid = new Guid()) {
// ...
}
public static void Main() {
// Faqat "guid" parametrini berish, qolganlarini standart qiymatda qoldirish
M(guid: Guid.NewGuid());
// Faqat "x" va "dt" parametrlarini berish
M(x: 5, dt: DateTime.Now);
}
Ko'rib turganingizdek, nomlangan argumentlar yordamida o'rtadagi ixtiyoriy parametrlarni "o'tkazib yuborish" va faqat kerakli parametrlarga qiymat berish mumkin.
- Barcha nomlangan argumentlar pozitsion argumentlardan keyin kelishi kerak.
- Argument nomi metod e'lonidagi parametr nomiga to'g'ri kelishi kerak. Bu shuni anglatadiki, agar metod ishlab chiquvchisi parametr nomini o'zgartirsa, chaqiruvchi kod ishlamay qolishi mumkin. Shuning uchun metod chaqiruvchilarini buzmaslik uchun parametr nomlarini ehtiyotkorlik bilan tanlang.
- Agar metod
paramsmassivini qabul qilsa,paramsdan keyin nomlangan argumentlarni berish mumkin emas.
Nomlangan argumentlarni ishlatganingizda parametr nomlari API ning bir qismiga aylanadi. Agar siz kutubxona muallifisiz va metod parametr nomini o'zgartirsangiz, sizning metodingizni nomlangan argumentlar bilan chaqiruvchi barcha kodlar kompilyatsiya xatosi berishi mumkin. Shuning uchun parametr nomlari haqida yaxshilab o'ylang.
Yashirin Turlangan Mahalliy O'zgaruvchilar (Implicitly Typed Local Variables)
C# var kalit so'zi yordamida kompilyatorga mahalliy o'zgaruvchining turini initsializatsiya ifodasi asosida avtomatik aniqlash imkonini beradi. Bu xususiyat yashirin turlanish (implicit typing) deb ataladi.
var Kalit So'zi
Quyidagi ikkita e'lon bir xil natija beradi:
// Oshkor (explicit) turlanish
private static void SomeMethod() {
Int32 x = 5;
String s = "Hello";
Dictionary<String, List<Int32>> dict =
new Dictionary<String, List<Int32>>();
}
// Yashirin (implicit) turlanish — var bilan
private static void SomeMethod() {
var x = 5; // x turi: Int32
var s = "Hello"; // s turi: String
var dict = new Dictionary<String, List<Int32>>();
// dict turi: Dictionary<String, List<Int32>>
}
Yuqoridagi ikki versiya bir xil IL kod hosil qiladi. var kalit so'zi shunchaki sintaktik qulaylik bo'lib, kompilyator o'ng tomondagi ifodadan turni aniqlaydi va uni kompilyatsiya vaqtida almashtirib qo'yadi. Bu dinamik turlanish emas — tur kompilyatsiya vaqtida aniqlanadi va keyinchalik o'zgarmaydi.
varfaqat mahalliy o'zgaruvchilar uchun ishlatilishi mumkin. Maydonlar, parametrlar yoki qaytish turlari uchunvarishlatib bo'lmaydi.- O'zgaruvchi e'lon qilingan joyda initsializatsiya qilinishi shart.
var x;yozish mumkin emas — kompilyator turni aniqlay olmaydi. - Initsializatsiya ifodasi
nullbo'lishi mumkin emas —var x = null;kompilyatsiya xatosi beradi, chunkinulldan turni aniqlash imkonsiz.
var ni ishlatish yoki ishlatmaslik ko'pincha shaxsiy afzallik masalasidir. Ba'zi ishlab chiquvchilar var ni kodning o'qilishini yaxshilaydi deb hisoblaydi (ayniqsa uzun generic turlar bilan), boshqalari esa oshkor tur yozishni afzal ko'radi. Richter shaxsan oshkor turlanishni afzal ko'radi, chunki u kodni o'qish paytida turning nima ekanligini darhol ko'rish imkonini beradi.
// var ning foydali holati — uzun tur nomlarini takrorlamaslik
var nameToAge = new Dictionary<String, List<Int32>>();
// var ning zararli holati — tur noma'lum
var result = SomeMethod(); // result nima tur ekan?
// Aniq tur — o'qishga oson
Dictionary<String, List<Int32>> nameToAge =
new Dictionary<String, List<Int32>>();
// Aniq tur — metod qaytish turini ko'rish oson
Int32 result = SomeMethod();
C# dagi var JavaScript dagi var dan butunlay farq qiladi. C# da var statik turlanishdir — kompilyator turni aniqlaydi va IL kodda aniq turni yozadi. Bu hech qanday runtime yuklanishiga olib kelmaydi. JavaScript da esa var dinamik turlanishdir. C# dagi var ni dynamic kalit so'zi bilan adashtirmang — dynamic haqiqatdan ham runtime turlanishdir va boshqa narsa.
Parametrlarni Havola Orqali Uzatish (Passing Parameters by Reference)
CLR da odatda parametrlar qiymat bo'yicha (by value) uzatiladi. Bu value turlar uchun qiymatning nusxasi yaratilishini, reference turlar uchun esa havolaning nusxasi uzatilishini anglatadi.
Ammo C# sizga parametrlarni havola bo'yicha (by reference) ham uzatish imkonini beradi. Bu out va ref kalit so'zlari yordamida amalga oshiriladi.
CLR nuqtai nazaridan out va ref bir xil — ikkala holda ham parametrga ko'rsatkich (pointer) uzatiladi. Ammo C# kompilyatori ularni farqli ko'radi va tarkibiy qoidalarni majburlaydi.
out Kalit So'zi
out kalit so'zi metod ichida parametrga qiymat tayinlanishi shart ekanligini bildiradi. Chaqiruvchi metod chaqiruvdan oldin o'zgaruvchini initsializatsiya qilishi shart emas. Metod esa o'zgaruvchiga qiymat yozishi kerak.
public sealed class Program {
public static void Main() {
Int32 x; // x initsializatsiya qilinmagan
GetVal(out x); // x havola orqali uzatildi
Console.WriteLine(x); // "10" ko'rsatadi
}
private static void GetVal(out Int32 v) {
v = 10; // Bu metod v ga qiymat BERISHI SHART
}
}
Yuqoridagi misolda x o'zgaruvchisi GetVal chaqirilgunga qadar initsializatsiya qilinmagan bo'lsa ham, out parametr sifatida uzatilganligi uchun bu to'g'ri. GetVal metodi esa v ga qiymat berishi majburiy — aks holda kompilyatsiya xatosi beradi.
C# 7.0 dan boshlab, out o'zgaruvchisini to'g'ridan-to'g'ri metod chaqiruvi ichida e'lon qilish mumkin:
// C# 7.0 dan oldin
Int32 x;
GetVal(out x);
// C# 7.0 dan boshlab — inline e'lon
GetVal(out Int32 x);
Console.WriteLine(x); // x bu yerda mavjud
ref Kalit So'zi
ref kalit so'zi parametrning allaqachon initsializatsiya qilingan o'zgaruvchiga ishora qilishini talab qiladi. Metod ref parametr qiymatini o'qishi ham, yozishi ham mumkin.
public sealed class Program {
public static void Main() {
Int32 x = 5; // x 5 ga initsializatsiya qilingan
AddVal(ref x); // x havola orqali uzatildi
Console.WriteLine(x); // "15" ko'rsatadi (5 + 10 = 15)
}
private static void AddVal(ref Int32 v) {
v += 10; // v ni o'qish va yozish mumkin
}
}
Bu misolda x o'zgaruvchisi oldindan initsializatsiya qilingan bo'lishi shart. AddVal metodi v ning joriy qiymatini o'qiydi (5) va unga 10 qo'shadi. Metod qaytganida x ning qiymati 15 ga teng bo'ladi.
ref va out Orasidagi Farqlar
Quyidagi jadvalda ref va out kalit so'zlari orasidagi asosiy farqlar ko'rsatilgan:
| Xususiyat | out | ref |
|---|---|---|
| Chaqiruvchi o'zgaruvchini oldindan initsializatsiya qilishi kerakmi? | Yo'q | Ha |
| Metod parametrga qiymat berishi shartmi? | Ha | Yo'q |
| Metod parametrdan qiymat o'qishi mumkinmi? | Faqat qiymat berilgandan keyin | Ha (darhol) |
| CLR dagi farq | CLR uchun ikkisi bir xil — ikkala holda ham ko'rsatkich uzatiladi | |
C# da ref va out faqatgina metod imzolarida farqlanadi. Ammo siz bitta metodning ref va out versiyalarini bir vaqtda overload qila olmaysiz, chunki CLR uchun ular bir xil. Quyidagi kod kompilyatsiya xatosi beradi:
// Kompilyatsiya XATOSI: ref va out bilan overload qilish mumkin emas
public sealed class SomeType {
public static void M(ref Int32 x) { ... }
public static void M(out Int32 x) { ... } // XATO!
}
Lekin ref/out li va ref/out siz versiyalarni overload qilish mumkin:
// Bu TO'G'RI — biri ref bilan, biri refsiz
public sealed class SomeType {
public static void M(Int32 x) { ... }
public static void M(ref Int32 x) { ... } // TO'G'RI
}
Value va Reference Turlarni Havola Orqali Uzatish
Havola bo'yicha uzatish value turlar uchun ham, reference turlar uchun ham ishlaydi, ammo ularning ta'siri farqli:
Value turlarni havola orqali uzatish
Value turlar havola orqali uzatilganda, metod chaqiruvchining o'zgaruvchisining qiymatini bevosita o'zgartirishi mumkin:
public static void Main() {
Int32 x = 5;
Console.WriteLine(x); // "5" ko'rsatadi
AddVal(ref x);
Console.WriteLine(x); // "15" ko'rsatadi
}
private static void AddVal(ref Int32 v) {
v += 10; // Chaqiruvchining x o'zgaruvchisini o'zgartiradi
}
Reference turlarni havola orqali uzatish
Reference turlar havola orqali uzatilganda, metod chaqiruvchining o'zgaruvchisining havolasini (qaysi obyektga ishora qilishi) o'zgartirishi mumkin:
public static void Main() {
String s = "Hello";
Console.WriteLine(s); // "Hello" ko'rsatadi
// ref BILAN — havola o'zgartirilishi mumkin
ChangeRef(ref s);
Console.WriteLine(s); // "Changed" ko'rsatadi
}
private static void ChangeRef(ref String text) {
// Chaqiruvchining s o'zgaruvchisini yangi obyektga yo'naltiradi
text = "Changed";
}
// ref siz — havola o'zgarmaydi
private static void NoChange(String text) {
text = "Changed"; // Bu faqat mahalliy nusxani o'zgartiradi
// Chaqiruvchining s o'zgaruvchisi o'zgarmaydi
}
Ikki o'zgaruvchi qiymatini almashtirish uchun ref kerak:
public static void Swap(ref Object a, ref Object b) {
Object t = b;
b = a;
a = t;
}
public static void Main() {
String s1 = "Hello";
String s2 = "World";
// XATO: String Object ga mos kelmaydi ref da
// Swap(ref s1, ref s2); // Kompilyatsiya xatosi!
// Turlar aniq mos kelishi kerak
Object o1 = s1, o2 = s2;
Swap(ref o1, ref o2); // TO'G'RI
}
ref yoki out parametr ishlatilganda, uzatiladigan o'zgaruvchining turi parametr turiga aniq mos kelishi kerak. Bu tur xavfsizligini ta'minlash uchun zarur. Masalan, ref Object kutayotgan metodga ref String uzatib bo'lmaydi — chunki metod Object o'rniga boshqa tur yozishi mumkin va bu String o'zgaruvchisini buzadi.
Bu muammoni generik metod yordamida hal qilish mumkin:
public static void Swap<T>(ref T a, ref T b) {
T t = b;
b = a;
a = t;
}
public static void Main() {
String s1 = "Hello";
String s2 = "World";
Swap(ref s1, ref s2); // TO'G'RI! T = String
Console.WriteLine(s1); // "World"
Console.WriteLine(s2); // "Hello"
}
Metodga O'zgaruvchan Sonli Argumentlarni Uzatish
Ba'zan metodga noma'lum sonli argumentlarni uzatish kerak bo'ladi. Masalan, bir nechta sonni qo'shib yig'indisini qaytaruvchi metodga 2 ta, 5 ta yoki 100 ta son uzatishingiz mumkin.
params Kalit So'zi
params kalit so'zi yordamida metod o'zgaruvchan sonli argumentlarni qabul qilishi mumkin. Buning uchun metodning oxirgi parametri params bilan belgilangan bir o'lchamli massiv bo'lishi kerak.
public sealed class Program {
// O'zgaruvchan sonli Int32 argumentlarni qabul qiluvchi metod
private static Int32 Add(params Int32[] values) {
// ESLATMA: agar kerak bo'lsa, bu metodni
// "values" ni IEnumerable<Int32> qabul qiluvchi qilib yozish mumkin
Int32 sum = 0;
if (values != null) {
for (Int32 x = 0; x < values.Length; x++)
sum += values[x];
}
return sum;
}
public static void Main() {
// "15" ko'rsatadi
Console.WriteLine(Add(new Int32[] { 1, 2, 3, 4, 5 }));
}
}
Massivni oshkor yaratib uzatish to'g'ri ishlaydi, ammo bu biroz noqulay. Biz dasturchilar sifatida quyidagicha yozishni afzal ko'ramiz:
public static void Main() {
// "15" ko'rsatadi
Console.WriteLine(Add(1, 2, 3, 4, 5));
}
params kalit so'zi tufayli buni amalga oshirish mumkin. params kalit so'zi kompilyatorga parametrga System.ParamArrayAttribute atributini qo'llashni aytadi.
C# kompilyatori metod chaqiruvini aniqlasa, belgilangan nomli barcha metodlarni tekshiradi. Agar ParamArray atributi bo'lmagan metod chaqiruvga mos kelsa, kompilyator shu metodni chaqirish uchun kerakli kodni yaratadi. Ammo agar kompilyator mos keluvchi metod topa olmasa, u ParamArray atributiga ega metodlarni tekshiradi. Agar shunday metod topilsa, kompilyator massiv yaratuvchi va uni elementlari bilan to'ldiruvchi kodni hosil qiladi va so'ng shu tanlangan metodga chaqiruv amalga oshiradi.
Yuqoridagi misolda Add(1, 2, 3, 4, 5) chaqiruvi aslida kompilyator tomonidan quyidagiga aylantiriladi:
// Kompilyator aslida shu kodni hosil qiladi:
Console.WriteLine(Add(new Int32[] { 1, 2, 3, 4, 5 }));
Faqat metodning oxirgi parametri params (ParamArrayAttribute) bilan belgilanishi mumkin. Bu parametr bir o'lchamli massivni aniqlashi kerak va istalgan tur bo'lishi mumkin. Shuningdek, null yoki 0 elementli massiv uzatish qonuniydir. Quyidagi Add chaqiruvlari to'g'ri ishlaydi va natija 0 bo'ladi:
public static void Main() {
// Ikkala satr ham "0" ko'rsatadi
Console.WriteLine(Add()); // Add ga new Int32[0] uzatadi
Console.WriteLine(Add(null)); // Add ga null uzatadi: samaraliroq (massiv yaratilmaydi)
}
O'zgaruvchan Sonli Argumentlar va Samaradorlik
Ixtiyoriy sonli argumentlarni qabul qiluvchi har qanday parametrga ega turlar massivga ega. Agar siz oshkor ravishda null bermansangiz, o'zgaruvchan sonli argumentlarni qabul qiluvchi metodga har bir chaqiruv massiv obyektini heap da yaratilishiga, massiv elementlarining initsializatsiya qilinishiga va massiv xotirasining chiqindi yig'ish (garbage collection) bilan tozalanishiga olib keladi.
O'zgaruvchan sonli argumentlarni qabul qiluvchi metodga chaqiruv qo'shimcha samaradorlik yuklanishiga olib keladi, agar siz oshkor ravishda null bermasangiz. Chunki har bir chaqiruvda massiv obyekti heapda yaratiladi, elementlari initsializatsiya qilinadi va oxir-oqibat chiqindi yig'ish tomonidan tozalanadi. Bu yuklanishni kamaytirish uchun, siz params kalit so'zini ishlatmaydigan bir nechta overload qilingan metodlarni aniqlashni ko'rib chiqishingiz mumkin. Misol sifatida System.String klassining Concat metodiga qarang:
public sealed class String : Object, ... {
public static string Concat(object arg0);
public static string Concat(object arg0, object arg1);
public static string Concat(object arg0, object arg1, object arg2);
public static string Concat(params object[] args);
public static string Concat(string str0, string str1);
public static string Concat(string str0, string str1, string str2);
public static string Concat(string str0, string str1, string str2, string str3);
public static string Concat(params string[] values);
}
Ko'rib turganingizdek, Concat metodi params kalit so'zini ishlatmaydigan bir nechta overloadlarga ega. Concat metodining bu versiyalari eng ko'p chaqiriladigan overloadlar bo'lib, ular eng keng tarqalgan holatlar uchun samaradorlikni oshirish maqsadida mavjud. params kalit so'zini ishlatadigan overloadlar kamroq uchraydigan holatlar uchun mo'ljallangan — bu holatlarda samaradorlik yuklanishi bo'lsa ham, xayriyatki, bunday holatlar kamdan-kam uchraydi.
Agar parametr turining o'zi har qanday bo'lishi mumkin bo'lsa nima qilish kerak? Javob oddiy: metod prototipini Int32[] o'rniga Object[] qabul qiladigan qilib o'zgartiring. Mana, uzatilgan har bir obyektning turini ko'rsatadigan metod:
public sealed class Program {
public static void Main() {
DisplayTypes(new Object(), new Random(), "Jeff", 5);
}
private static void DisplayTypes(params Object[] objects) {
if (objects != null) {
foreach (Object o in objects)
Console.WriteLine(o.GetType());
}
}
}
Bu kodni ishga tushirish quyidagi natijani beradi:
System.Object
System.Random
System.String
System.Int32
Parametr va Qaytish Turi Yo'riqnomalari (Parameter and Return Type Guidelines)
Metod parametrlarining turlarini e'lon qilayotganda, eng zaif turni (weakest type) ko'rsatishga harakat qiling, interfeyslarni asosiy klasslardan afzal ko'ring. Masalan, agar siz elementlar to'plamini boshqaradigan metod yozayotgan bo'lsangiz, metod parametrini List<T> kabi kuchli (strong) ma'lumot turi yoki ICollection<T> yoki IList<T> kabi kuchliroq interfeys turi o'rniga IEnumerable<T> kabi zaif interfeys turi bilan e'lon qilish yaxshiroq.
Zaif Parametr Turlari
// Afzal: Bu metod zaif parametr turini ishlatadi
public void ManipulateItems<T>(IEnumerable<T> collection) { ... }
// Nomaqbul: Bu metod kuchli parametr turini ishlatadi
public void ManipulateItems<T>(List<T> collection) { ... }
Sababi shuki, birinchi metodga massiv, List<T> obyekti, String obyekti va boshqalar — IEnumerable<T> ni implement qiluvchi istalgan obyekt uzatilishi mumkin. Ikkinchi metod esa faqat List<T> obyektlarini qabul qiladi; u massiv yoki String obyektini qabul qilmaydi. Birinchi metod aniq yaxshiroq, chunki u ancha moslashuvchan va ko'proq stsenariylarda ishlatilishi mumkin.
Tabiiyki, agar siz ro'yxat (faqat har qanday sanaladigan obyekt emas) talab qiladigan metod yozayotgan bo'lsangiz, parametr turini IList<T> deb e'lon qilishingiz kerak. Siz baribir parametr turini List<T> deb e'lon qilishdan saqlaning. IList<T> dan foydalanish chaqiruvchiga massivlar va IList<T> ni implement qiluvchi boshqa obyektlarni uzatish imkonini beradi.
Misol sifatida, interfeysga asoslangan arxitektura o'rniga asosiy klassga asoslangan arxitekturani ko'rib chiqsak, agar siz oqimdan (stream) baytlarni qayta ishlovchi metod yozayotgan bo'lsangiz, quyidagiga ega bo'lasiz:
// Afzal: Bu metod zaif parametr turini ishlatadi
public void ProcessBytes(Stream someStream) { ... }
// Nomaqbul: Bu metod kuchli parametr turini ishlatadi
public void ProcessBytes(FileStream fileStream) { ... }
Birinchi metod istalgan turdagi oqimdan baytlarni qayta ishlashi mumkin: FileStream, NetworkStream, MemoryStream va boshqalar. Ikkinchi metod esa faqat FileStream da ishlashi mumkin, bu uni ancha cheklangan qiladi.
Kuchli Qaytish Turlari
Boshqa tomondan, metodning qaytish turini iloji boricha eng kuchli tur (strongest type) bilan e'lon qilish yaxshiroq (o'zingizni aniq turga bog'lamaslikka harakat qilib). Masalan, Stream obyektini qaytarish o'rniga FileStream obyektini qaytaruvchi metodga e'lon berish yaxshiroq:
// Afzal: Bu metod kuchli qaytish turini ishlatadi
public FileStream OpenFile() { ... }
// Nomaqbul: Bu metod zaif qaytish turini ishlatadi
public Stream OpenFile() { ... }
Bu birinchi metod afzal, chunki u metod chaqiruvchisiga qaytarilgan obyektni FileStream obyekti yoki Stream obyekti sifatida ko'rish imkonini beradi. Ikkinchi metod esa chaqiruvchini qaytarilgan obyektni faqat Stream obyekti sifatida ko'rishga majbur qiladi. Asosan, chaqiruvchiga metod chaqirganda iloji boricha ko'proq moslashuvchanlikka ega bo'lish imkonini berish yaxshi, bu metodning eng keng stsenariylarda ishlatilishiga imkon beradi.
Ba'zan metodning ichki amalga oshirilishini chaqiruvchilarga ta'sir qilmasdan o'zgartirish imkoniyatini saqlab qolishni xohlaysiz. Yuqoridagi misolda OpenFile metodi FileStream dan boshqa narsani qaytarishi dargumon (yoki turni FileStream dan kelib chiqqan obyekt). Ammo, agar sizda List<String> qaytaradigan metod bo'lsa va siz kelajakda ichki amalga oshirishni String[] qaytaradigan qilib o'zgartirishni xohlasangiz, zaifroq qaytish turini tanlang:
// Moslashuvchan: Bu metod zaifroq qaytish turini ishlatadi
public IList<String> GetStringCollection() { ... }
// Moslashuvchan emas: Bu metod kuchli qaytish turini ishlatadi
public List<String> GetStringCollection() { ... }
Bu misolda GetStringCollection metodi ichki ravishda List<String> obyektini ishlatsa va qaytarsa ham, metodni IList<String> qaytaradigan deb prototiplash yaxshiroq. Kelajakda GetStringCollection metodi o'zining ichki to'plamini String[] ga o'zgartirsa ham, metod chaqiruvchilari o'zlarining manba kodida hech narsa o'zgartirishi shart bo'lmaydi. Aslida ular kodni qayta kompilyatsiya qilishlari ham shart emas.
E'tibor bering: men bu misolda eng kuchli zaif turlardan foydalanayapman. Masalan, men IEnumerable<String> yoki hatto ICollection<String> dan foydalanmayapman.
Const-lik (Const-ness)
Ba'zi dasturlash tillarida, masalan boshqarilmagan (unmanaged) C++ da, metodlar yoki parametrlarni const deb e'lon qilish mumkin — bu instansiya metodidagi kodning obyektning maydonlarini o'zgartirishini taqiqlaydi yoki metodga uzatilgan obyektlarning o'zgartirilishining oldini oladi. CLR bunday xususiyatni ta'minlamaydi va ko'plab dasturchilar bu yetishmayotgan imkoniyat haqida afsuslangan. CLR bu xususiyatni taqdim etmaganligi sababli, hech qanday til (jumladan C#) uni taklif qila olmaydi.
Avvalo shuni ta'kidlash kerakki, boshqarilmagan C++ da instansiya metodini yoki parametrni const deb belgilash dasturchi oddiy kodni yoza olmasligini kafolatladi — bu kod obyektni yoki parametrni o'zgartirishi mumkin edi. Metod ichida doimo const-likni bekor qilish (const-ness ni cast away qilish) yoki obyekt/argumentning manzilini olib, u manzilga yozish orqali obyektni o'zgartirishi mumkin bo'lgan kod yozish imkoni bor edi. Bir ma'noda, boshqarilmagan C++ dasturchilarga yolg'on gapirar edi — ularga o'zlarining konstant obyektlari/argumentlari yozilishi mumkin emasligiga ishontirgan holda, aslida ular yozilishi mumkin edi.
Turni loyihalashda ishlab chiquvchi shunchaki obyekt/argumentlarni o'zgartiradigan kod yozishdan saqlanishi mumkin. Masalan, satrlar (strings) o'zgarmasdir (immutable), chunki String klassi satr obyektini o'zgartira oladigan hech qanday metodni taklif qilmaydi.
Shuningdek, Microsoft uchun CLR ga konstantlik tekshiruvi imkoniyatini qo'shish juda qiyin bo'lishi mumkin edi. CLR har bir yozish amalida yozish konstantli obyektga emas ekanligini tekshirishi kerak bo'lar edi va bu samaradorlikka sezilarli zarar yetkazar edi. Aniqlangan buzilish CLR da istisno (exception) chiqarishga olib kelgan bo'lar edi. Bundan tashqari, konstantlikni qo'llab-quvvatlash ishlab chiquvchilar uchun juda ko'p murakkablik qo'shadi. Masalan, agar tur o'zgarmas bo'lsa, barcha hosilaviy turlar ham buni hurmat qilishi kerak edi. Bundan tashqari, o'zgarmas tur ehtimol faqat o'zgarmas turlardan iborat maydonlardan tashkil topishi kerak bo'lar edi.
Bular CLR ning konstantli obyektlar/argumentlarni qo'llab-quvvatlamasligining ba'zi sabablari xolos.