5-Bob: Primitiv, Reference va Value Turlar
Dasturlash tili primitiv turlari, reference va value turlarning farqlari, boxing/unboxing mexanizmi va dynamic turi
Ushbu bobda men dasturlash tillarida ishlatiladigan turlarning asosiy kategoriyalarini muhokama qilaman. Xususan, siz dasturlash tili taklif qiladigan primitiv turlar, reference (havola) turlar va value (qiymat) turlarning CLR bilan qanday ishlashini o'rganasiz. Shuningdek, boxing va unboxing mexanizmini chuqur tushunasiz va bu jarayonlar ilovangiz ishlashiga qanday ta'sir qilishini ko'rasiz.
Dasturlash Tili Primitiv Turlari
Ba'zi ma'lumot turlari shunchalik keng qo'llaniladiki, ko'plab kompilyatorlar ularga maxsus sintaksis yordamida murojaat qilish imkonini beradi. Masalan, siz System.Int32 obyektini quyidagicha yaratishingiz mumkin:
System.Int32 a = new System.Int32();
Lekin buni yozish ancha og'ir. Yaxshiyamki, ko'plab kompilyatorlar (jumladan C#) sizga qisqaroq sintaksisdan foydalanish imkonini beradi:
int a = 0;
Bu kod avvalgi bilan bir xil IL kodga kompilyatsiya qilinadi va bir xil ishlaydi. Kompilyator to'g'ridan-to'g'ri qo'llab-quvvatlaydigan har qanday ma'lumot turi primitiv tur (primitive type) deb ataladi. Primitiv turlar to'g'ridan-to'g'ri Framework Class Library (FCL) dagi turlarga moslanadi.
Quyida C# primitiv turlari va ularning FCL dagi mosliklari ko'rsatilgan:
| C# primitiv turi | FCL turi | CLS mos | Tavsif |
|---|---|---|---|
sbyte | System.SByte | Yo'q | Imzolangan 8-bit qiymat |
byte | System.Byte | Ha | Imzosiz 8-bit qiymat |
short | System.Int16 | Ha | Imzolangan 16-bit qiymat |
ushort | System.UInt16 | Yo'q | Imzosiz 16-bit qiymat |
int | System.Int32 | Ha | Imzolangan 32-bit qiymat |
uint | System.UInt32 | Yo'q | Imzosiz 32-bit qiymat |
long | System.Int64 | Ha | Imzolangan 64-bit qiymat |
ulong | System.UInt64 | Yo'q | Imzosiz 64-bit qiymat |
char | System.Char | Ha | 16-bit Unicode belgi |
float | System.Single | Ha | 32-bit suzuvchi nuqta |
double | System.Double | Ha | 64-bit suzuvchi nuqta |
bool | System.Boolean | Ha | true/false qiymat |
decimal | System.Decimal | Ha | 128-bit yuqori aniqlikdagi qiymat |
string | System.String | Ha | Belgilar massivi |
object | System.Object | Ha | Barcha turlarning asosiy turi |
dynamic | System.Object | Ha | CLR uchun Object bilan bir xil |
C# kompilyatorida primitiv turlar o'rtasidagi yashirin konvertatsiya (implicit conversion) qo'llaniladi. Masalan, int qiymatini long ga ma'lumot yo'qotmasdan o'tkazish mumkin. Lekin long dan int ga o'tishda aniq konvertatsiya (explicit cast) kerak, chunki ma'lumot yo'qolishi mumkin.
Int32 i = 5; // Int32 dan Int32 ga — yashirin
Int64 l = i; // Int32 dan Int64 ga — yashirin (xavfsiz)
Single s = i; // Int32 dan Single ga — yashirin
Byte b = (Byte) i; // Int32 dan Byte ga — aniq cast kerak
Int16 v = (Int16) s; // Single dan Int16 ga — aniq cast kerak
Primitiv turlardan foydalanishda doim ehtiyot bo'ling. Turli turlar o'rtasida konvertatsiya qilishda ma'lumot yo'qolishi yuz berishi mumkin. Masalan:
// Overflow misoli
Byte b = 100;
b = (Byte)(b + 200); // b endi 44 bo'ladi (300 - 256 = 44)
Checked va Unchecked Amallar
Hisoblashlar kutilmagan natijalar berishi mumkin. Odatda, bu foydalanuvchining noto'g'ri kiritishi yoki dasturchi kutmagan tizim qiymatlari sababli bo'ladi. Shu sababli men dasturchilarga quyidagilarni tavsiya qilaman:
- Imzosiz turlar (
UInt32,UInt64) o'rniga imzolangan turlardan (Int32,Int64) foydalaning. Bu kompilyatorga overflow/underflow xatoliklarini aniqlash imkonini beradi. - Kutilmagan overflow sodir bo'lishi mumkin bo'lgan kodda
checkedbloklardan foydalaning. - Overflow qabul qilinadigan kodda (masalan, checksum hisoblash)
uncheckedbloklardan foydalaning.
C# da checked va unchecked operatorlari yoki bloklari bilan overflow tekshirishni boshqarishingiz mumkin:
// checked blokda overflow OverflowException tashlaydi
checked {
Byte b = 100;
b = (Byte)(b + 200); // OverflowException!
}
// unchecked blokda overflow jim o'tadi
unchecked {
Byte b = 100;
b = (Byte)(b + 200); // b = 44, xatolik yo'q
}
Dasturni ishlab chiqish paytida kompilyatorning /checked+ kalitidan foydalaning. Bu debug buildlarda barcha arifmetik amallarni tekshiradi. Reliz buildlarda esa /checked- ishlating, shunda kod tezroq ishlaydi.
System.Decimal turi juda maxsus turdir. Garchi ko'plab dasturlash tillari (C# va Visual Basic jumladan) Decimal ni primitiv tur deb hisoblasa ham, CLR buni qilmaydi. CLR da Decimal qiymatlarini manipulyatsiya qiladigan IL ko'rsatmalari yo'q. Decimal turida Add, Subtract, Multiply, Divide kabi public statik metodlar mavjud. Decimal qiymatlari bilan ishlash CLR primitiv turlariga qaraganda sekinroq.
Shuningdek, System.Numerics.BigInteger turi ham maxsus bo'lib, u ichki tarzda UInt32 massividan foydalanadi va ixtiyoriy katta butun sonlarni ifodalash imkonini beradi. BigInteger ustidagi amallar hech qachon OverflowException bermaydi, lekin qiymat juda katta bo'lsa, OutOfMemoryException tashlashi mumkin.
Reference Turlar va Value Turlar
CLR ikki xil turni qo'llab-quvvatlaydi: reference turlar (havola turlar) va value turlar (qiymat turlar). FCL dagi turlarning aksariyati reference turlar bo'lsa-da, dasturchilar ko'pincha value turlardan foydalanishadi.
Reference turlar doimo managed heap da joylashtiriladi va C# dagi new operatori obyektning xotira manzilini qaytaradi. Reference turlar bilan ishlashda quyidagi jihatlarni hisobga olish kerak:
- Xotira managed heap dan ajratilishi kerak
- Heapda joylashtirilgan har bir obyektda qo'shimcha overhead a'zolar (tur obyekt ko'rsatkichi va sinxronizatsiya blok indeksi) bor, ular initsializatsiya qilinishi kerak
- Obyektdagi boshqa baytlar doimo nolga o'rnatiladi
- Managed heapdan obyekt ajratish garbage collection jarayonini ishga tushirishi mumkin
Agar har bir tur reference tur bo'lganida, ilova ishlashi jiddiy pasaygan bo'lardi. Har safar Int32 qiymatini ishlatganingizda xotira ajratish kerak bo'lishi mumkinligini tasavvur qiling!
Ishlashni yaxshilash uchun CLR value turlar (qiymat turlar) deb nomlangan engil turlarni taklif qiladi. Value tur instansiyalari odatda oqimning stekida joylashtiriladi (garchi ular reference tur obyekti ichida maydon sifatida ham joylashishi mumkin). Instansiyani ifodalovchi o'zgaruvchi instansiyaga ko'rsatkich emas — o'zgaruvchining o'zi instansiya maydonlarini o'z ichiga oladi.
Reference va Value Turlarning Farqlari
.NET Framework SDK hujjatlari qaysi turlar reference, qaysilari value ekanligini aniq ko'rsatadi. Hujjatlarda tur class deb ko'rsatilsa — u reference tur, structure yoki enumeration deb ko'rsatilsa — u value tur.
Barcha structurelar bevosita System.ValueType abstrakt turdan hosila bo'ladi. System.ValueType o'zi esa System.Object dan bevosita hosila bo'ladi. Barcha enumeratsiyalar System.Enum abstrakt turdan hosila bo'ladi.
Quyidagi kod va 5-2 rasm reference va value turlarning qanday farqlanishini ko'rsatadi:
// Reference tur ('class' kalit so'zi tufayli)
class SomeRef { public Int32 x; }
// Value tur ('struct' kalit so'zi tufayli)
struct SomeVal { public Int32 x; }
static void ValueTypeDemo() {
SomeRef r1 = new SomeRef(); // Heapda joylashtiriladi
SomeVal v1 = new SomeVal(); // Stekda joylashtiriladi
r1.x = 5; // Ko'rsatkich orqali murojaat
v1.x = 5; // Stekda o'zgartiriladi
Console.WriteLine(r1.x); // "5" ko'rsatadi
Console.WriteLine(v1.x); // "5" ko'rsatadi
SomeRef r2 = r1; // Faqat havola nusxalanadi
SomeVal v2 = v1; // Stekda a'zolar nusxalanadi
r1.x = 8; // r1.x VA r2.x ni o'zgartiradi
v1.x = 9; // Faqat v1.x ni o'zgartiradi
Console.WriteLine(r1.x); // "8" ko'rsatadi
Console.WriteLine(r2.x); // "8" ko'rsatadi
Console.WriteLine(v1.x); // "9" ko'rsatadi
Console.WriteLine(v2.x); // "5" ko'rsatadi
}
Bu kodda r2 = r1 faqat havolani nusxalaydi — ikkala o'zgaruvchi bitta obyektga ishora qiladi. Lekin v2 = v1 barcha a'zolarni nusxalaydi — ikkala o'zgaruvchi mustaqil instansiyalar.
O'z turlaringizni loyihalashda value tur quyidagi shartlar bajarilsa mos keladi:
- Tur primitiv tur sifatida ishlaydi — oddiy va immutable (o'zgarmas)
- Boshqa turdan meros olish kerak emas
- Boshqa turlar undan hosila bo'lmaydi
- Instansiyalar kichik (taxminan 16 bayt yoki undan kam)
- Instansiyalar katta bo'lsa, metod parametri yoki qaytariladigan qiymat sifatida ishlatilmaydi
Value turlarning asosiy afzalligi shundaki, ular managed heap da obyekt sifatida joylashtirilmaydi. Reference va value turlarning farqlari:
- Ikki shakl: Value tur obyektlari ikkita ko'rinishga ega — unboxed va boxed shakl. Reference turlar doimo boxed shaklda.
- System.ValueType: Value turlar
System.ValueTypedan hosila bo'ladi. Bu turEqualsmetodini ikki obyektning maydonlari mos kelishini tekshiradigan qilib qayta yozgan. - Yangi tur hosila qilish mumkin emas: Value turdan yangi reference yoki value tur hosila qilib bo'lmaydi. Barcha metodlar novirtual va sealed.
- null bo'lishi mumkin emas: Value tur o'zgaruvchisi doimo asosiy turning qiymatini o'z ichiga oladi.
nullreference tur o'zgaruvchisidekNullReferenceExceptionhosil qilish mumkin emas. - Maydon bo'yicha nusxalash: Value tur o'zgaruvchisini boshqasiga tayinlashda maydon bo'yicha nusxalash amalga oshiriladi.
- Mustaqil obyektlar: Ikki yoki undan ko'p reference tur o'zgaruvchilari heapdagi bitta obyektga ishora qilishi mumkin. Value tur o'zgaruvchilari esa mustaqil obyektlardir.
- Xotira ozod qilish: Unboxed value turlar heap da joylashtirilmaganligi sababli, ular uchun ajratilgan xotira metod tugashi bilan ozod qilinadi.
CLR Tur Maydonlarining Joylashuvini Boshqaradi
Ishlashni yaxshilash uchun CLR turning maydonlarini xotirada o'zi xohlagan tartibda joylashtirishga qodir. Masalan, CLR maydonlarni qayta tartiblashi mumkin, shunda obyekt havolalari birgalikda guruhlangan va ma'lumot maydonlari to'g'ri tekislanib paketlangan bo'lsin.
CLR ga nima qilish kerakligini System.Runtime.InteropServices.StructLayoutAttribute atributi orqali aytasiz:
LayoutKind.Auto— CLR maydonlarni o'zi tartibga soladiLayoutKind.Sequential— dasturchi belgilagan tartibda saqlanadiLayoutKind.Explicit— har bir maydonning aniq offseti ko'rsatiladi
using System;
using System.Runtime.InteropServices;
// CLR maydonlarni ishlash uchun o'zi joylashtiradi
[StructLayout(LayoutKind.Auto)]
internal struct SomeValType {
private readonly Byte m_b;
private readonly Int16 m_x;
...
}
Microsoft C# kompilyatori reference turlar (classlar) uchun LayoutKind.Auto, value turlar (structurelar) uchun esa LayoutKind.Sequential ni tanlaydi. Agar unmanaged kod bilan interop ishlatmasangiz, value turlaringiz uchun ham LayoutKind.Auto ishlatishingiz mumkin.
LayoutKind.Explicit bilan maydonlarning aniq offsetlarini ko'rsatish mumkin. Bu odatda C/C++ dagi union ni simulyatsiya qilish uchun ishlatiladi:
[StructLayout(LayoutKind.Explicit)]
internal struct SomeValType {
[FieldOffset(0)]
private readonly Byte m_b; // m_b va m_x maydonlari
[FieldOffset(0)] // bir-birining ustiga tushadi
private readonly Int16 m_x;
}
Reference tur va value tur bitta offsetda ustma-ust tushishi noqonuniy. Reference turlarni bir-birining ustiga joylashtirish mumkin, lekin bu tekshirib bo'lmaydi. Value turlarni bir-birining ustiga joylashtirish qonuniy, lekin barcha ustma-ust tushadigan baytlar public maydonlar orqali foydalanish mumkin bo'lishi kerak.
Boxing va Unboxing Value Turlari
Value turlar reference turlarga qaraganda engilroq, chunki ular managed heap da obyekt sifatida joylashtirilmaydi, garbage collect qilinmaydi va ko'rsatkichlar orqali murojaat qilinmaydi. Lekin ko'p hollarda value turning instansiyasiga havolani olishingiz kerak bo'ladi.
Masalan, ArrayList obyekti yaratib, unga Point structurelarini qo'shmoqchisiz:
// Value turni e'lon qilish
struct Point {
public Int32 x, y;
}
public sealed class Program {
public static void Main() {
ArrayList a = new ArrayList();
Point p; // Pointni ajratish (heapda emas)
for (Int32 i = 0; i < 10; i++) {
p.x = p.y = i; // Value tur a'zolarini initsializatsiya qilish
a.Add(p); // Value turni boxing qilib ArrayList ga qo'shish
}
...
}
}
ArrayList.Add metodi Object parametrini qabul qiladi. Point esa value tur. Shuning uchun value turni reference turga aylantirish kerak — bu jarayon boxing deb ataladi.
Value tur instansiyasi boxinglanganida ichki tarzda quyidagilar sodir bo'ladi:
- Xotira ajratish: Managed heap dan xotira ajratiladi. Hajm — value tur maydonlari + ikki qo'shimcha overhead a'zo (tur obyekt ko'rsatkichi va sinxronizatsiya blok indeksi).
- Maydonlar nusxalash: Value tur maydonlari yangi ajratilgan heap xotirasiga nusxalanadi.
- Manzil qaytarish: Obyektning manzili qaytariladi. Bu manzil endi obyektga havola — value tur reference turga aylandi.
C# kompilyatori avtomatik ravishda boxing uchun kerakli IL kodini yaratadi, lekin siz ichki jarayonni tushunishingiz kerak.
Unboxing va Boxing Misollari
Boxinglanganini bilganimizdan keyin, unboxing haqida gaplashaylik. ArrayList dan birinchi elementni olishni xohlaysiz deylik:
Point p = (Point) a[0];
Bu yerda siz ArrayList ning 0-elementidagi havolani olib, uni Point value tur instansiyasiga joylashtirishga harakat qilyapsiz. Buning uchun boxed Point obyektidagi barcha maydonlar stek dagi value tur o'zgaruvchisiga nusxalanishi kerak. CLR buni ikki bosqichda amalga oshiradi — avval boxed Point obyektidagi maydonlar manzilini aniqlaydi, keyin bu maydonlarni stekka nusxalaydi. Bu jarayon unboxing deb ataladi.
Unboxing boxingning to'liq teskarisi emas. Unboxing operatsiyasi aslida faqat boxed instansiya ichidagi xom qiymat ma'lumotlariga ko'rsatkich olish operatsiyasi. Boxingdan farqli o'laroq, unboxing xotirada hech qanday baytni nusxalashni o'z ichiga olmaydi. Lekin unboxing operatsiyasidan keyin odatda maydonlarni nusxalash amalga oshiriladi.
Unboxing qilishda to'liq nima sodir bo'ladi:
- Agar boxed value tur instansiyasiga havola
nullbo'lsa,NullReferenceExceptiontashlanadi. - Agar havola kerakli value turning boxed instansiyasiga ishora qilmasa,
InvalidCastExceptiontashlanadi.
public static void Main() {
Int32 x = 5;
Object o = x; // x ni boxing qilish — o boxed obyektga ishora qiladi
Int16 y = (Int16)(Int32) o; // Avval to'g'ri turga unbox, keyin cast
}
Boxing/unboxing operatsiyalari ilovangiz ishlashiga tezlik va xotira jihatidan ta'sir qiladi. Kompilyator bu amallarni avtomatik bajarishini bilishingiz va ularni minimallashtirishga harakat qilishingiz kerak.
FCL da generic kolleksiya klasslari mavjud bo'lib, ular non-generic klasslarga qaraganda afzalroq. Masalan, System.Collections.ArrayList o'rniga System.Collections.Generic.List<T> ishlatish kerak. Generic kolleksiyalar value turlar bilan boxing/unboxing qilmasdan ishlash imkonini beradi, bu ishlashni sezilarli yaxshilaydi.
Keling, yana bir misol ko'raylik:
public static void Main() {
Int32 v = 5; // Unboxed value tur o'zgaruvchisi
Object o = v; // o boxed Int32 (5 qiymatli) ga ishora qiladi
v = 123; // Unboxed qiymatni 123 ga o'zgartirish
Console.WriteLine(v); // "123" ko'rsatadi
v = (Int32) o; // o ni unbox qilib v ga nusxalash
Console.WriteLine(v); // "5" ko'rsatadi
}
Bu kodda nechta boxing operatsiyasi bor? Javob — bitta. Sababi System.Console klassi Int32 parametrini qabul qiladigan WriteLine metodini aniqlaydi. Shuning uchun v qiymati to'g'ridan-to'g'ri uzatiladi, boxing kerak emas.
Agar bitta value tur o'zgaruvchisini bir necha marta boxing qilish kerak bo'lsa, uni oldindan bir marta boxing qilib, boxed obyektga havolani qayta ishlatish samaraliroq:
using System;
public sealed class Program {
public static void Main() {
Int32 v = 5; // Unboxed value tur o'zgaruvchisi
// Samarasiz usul — v 3 marta boxing qilinadi
// Console.WriteLine("{0}, {1}, {2}", v, v, v);
// Samarali usul — v faqat 1 marta boxing qilinadi
Object o = v; // v ni qo'lda boxing qilish (faqat bir marta)
Console.WriteLine("{0}, {1}, {2}", o, o, o);
}
}
Boxed Value Turdagi Maydonlarni Interfeys Orqali O'zgartirish
Keling, value turlar, boxing va unboxingni qanchalik yaxshi tushunganingizni tekshirib ko'raylik. Quyidagi kodni ko'rib chiqing:
using System;
// Point — value tur
internal struct Point {
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y) {
m_x = x; m_y = y;
}
public void Change(Int32 x, Int32 y) {
m_x = x; m_y = y;
}
public override String ToString() {
return String.Format("({0}, {1})",
m_x.ToString(), m_y.ToString());
}
}
public sealed class Program {
public static void Main() {
Point p = new Point(1, 1);
Console.WriteLine(p); // "(1, 1)" - p boxing qilinadi
p.Change(2, 2);
Console.WriteLine(p); // "(2, 2)" - p yana boxing
Object o = p; // p uchinchi marta boxing
Console.WriteLine(o); // "(2, 2)"
((Point) o).Change(3, 3); // o ni unbox, VAQTINCHALIK Point yaratiladi
Console.WriteLine(o); // "(2, 2)" - hali ham eski qiymat!
}
}
Oxirgi qismi ko'plab dasturchilarni hayratga soladi. ((Point) o).Change(3, 3) satri o ni unbox qiladi va boxed Point maydonlarini stek dagi vaqtinchalik Point ga nusxalaydi. Vaqtinchalik pointning m_x va m_y maydonlari 3 ga o'zgartiriladi, lekin boxed Point ta'sirlanmaydi. WriteLine chaqirilganda to'rtinchi marta (2, 2) ko'rsatiladi.
C# da boxed value turning maydonlarini o'zgartirish mumkin emas. Lekin interfeys yordamida buni amalga oshirish mumkin:
using System;
// Change metodini aniqlaydi
internal interface IChangeBoxedPoint {
void Change(Int32 x, Int32 y);
}
// Point interfeysni amalga oshiradi
internal struct Point : IChangeBoxedPoint {
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y) { m_x = x; m_y = y; }
public void Change(Int32 x, Int32 y) { m_x = x; m_y = y; }
public override String ToString() {
return String.Format("({0}, {1})",
m_x.ToString(), m_y.ToString());
}
}
public sealed class Program {
public static void Main() {
Point p = new Point(1, 1);
Console.WriteLine(p); // "(1, 1)"
p.Change(2, 2);
Console.WriteLine(p); // "(2, 2)"
Object o = p;
Console.WriteLine(o); // "(2, 2)"
// p ni IChangeBoxedPoint ga cast — boxing sodir bo'ladi
((IChangeBoxedPoint) p).Change(4, 4);
Console.WriteLine(p); // "(2, 2)" — kutilmagan!
// o allaqachon boxed Point ga ishora qiladi
((IChangeBoxedPoint) o).Change(5, 5);
Console.WriteLine(o); // "(5, 5)" — kutilgan!
}
}
Men ushbu bobda avvalroq value turlar immutable bo'lishi kerakligini aytgan edim — ya'ni ular instansiya maydonlarini o'zgartiruvchi a'zolarni aniqlamasligi kerak. Value turlar maydonlarini readonly deb belgilash tavsiya etiladi. Oldingi misol value turlarning nima uchun immutable bo'lishi kerakligini aniq ko'rsatadi — boxing va unboxing/nusxalash jarayonlarida kutilmagan xatti-harakatlar yuz beradi.
Obyekt Tengligi va Identifikatsiyasi
Dasturchilar tez-tez obyektlarni bir-biri bilan solishtirish uchun kod yozishadi. Bu ayniqsa obyektlarni kolleksiyaga joylashganda, saralash, qidirish yoki solishtirish uchun muhim.
System.Object turi Equals virtual metodini taklif qiladi. Uning maqsadi — ikki obyekt bir xil qiymatga ega bo'lsa true qaytarish. Object.Equals ning standart implementatsiyasi quyidagicha:
public class Object {
public virtual Boolean Equals(Object obj) {
// Ikkala havola bitta obyektga ishora qilsa,
// ular bir xil qiymatga ega bo'lishi kerak
if (this == obj) return true;
// Obyektlar bir xil qiymatga ega emas deb faraz qilinadi
return false;
}
}
Bu standart implementatsiya aslida identifikatsiya (identity) ni tekshiradi, tenglik (equality) ni emas. Microsoft buni quyidagicha implementatsiya qilishi kerak edi:
public class Object {
public virtual Boolean Equals(Object obj) {
if (obj == null) return false;
if (this.GetType() != obj.GetType()) return false;
// Turlar bir xil bo'lsa va Object da maydon yo'q bo'lsa — true
return true;
}
}
Equals metodini qayta yozishda to'rtta xususiyatga rioya qilish kerak:
- Refleksivlik:
x.Equals(x)doimtrueqaytarishi kerak - Simmetriya:
x.Equals(y)vay.Equals(x)bir xil natija berishi kerak - Tranzitivlik:
x.Equals(y)vay.Equals(z)truebo'lsa,x.Equals(z)hamtruebo'lishi kerak - Barqarorlik: Qiymatlar o'zgarmagan bo'lsa, natija doim bir xil bo'lishi kerak
Identifikatsiyani tekshirish uchun Object.ReferenceEquals statik metodidan foydalaning. == operatoridan foydalanmang, chunki turlar bu operatorni qayta yuklab, boshqa semantikani berishi mumkin.
public class Object {
public static Boolean ReferenceEquals(Object objA, Object objB) {
return (objA == objB);
}
}
System.ValueType (barcha value turlarning asosiy klasi) Object.Equals ni qayta yozadi va qiymat tengligi tekshiruvini to'g'ri amalga oshiradi. Ichki tarzda ValueType.Equals reflection ishlatadi, shuning uchun sekin. O'z value turlaringizni aniqlashda Equals ni qayta yozib, o'z implementatsiyangizni taqdim etishingiz kerak.
Equals ni qayta yozishda qo'shimcha ikkita ishni ham bajarish tavsiya etiladi:
System.IEquatable<T>interfeysini amalga oshiring — tur-xavfsizEqualsmetodi==va!=operatorlarini qayta yuklang
Obyekt Xesh Kodlari
FCL dizaynerlari har qanday obyektni hash jadvali kolleksiyasiga joylashtirish imkoniyati juda foydali bo'lishini qaror qilishdi. Shu maqsadda System.Object virtual GetHashCode metodini taqdim qiladi.
Agar siz turni aniqlasangiz va Equals metodini qayta yozsangiz, GetHashCode ni ham qayta yozishingiz kerak. C# kompilyatori Equals ni qayta yozib, GetHashCode ni qayta yozmagan turlar uchun ogohlantirish beradi.
Sababi — System.Collections.Hashtable, System.Collections.Generic.Dictionary va shunga o'xshash kolleksiyalar teng obyektlarning bir xil hash kodiga ega bo'lishini talab qiladi.
Hash kodni hisoblash algoritmini tanlashda quyidagi ko'rsatmalarga amal qiling:
- Yaxshi tasodifiy taqsimot beradigan algoritmdan foydalaning
- Algoritmingiz kamida bitta instansiya maydonini ishlatishi kerak
- Algoritmda ishlatiladigan maydonlar immutable bo'lishi kerak
- Algoritm imkon qadar tez ishlashi kerak
- Bir xil qiymatli obyektlar bir xil hash kodni qaytarishi kerak
internal sealed class Point {
private readonly Int32 m_x, m_y;
public override Int32 GetHashCode() {
return m_x ^ m_y; // m_x XOR m_y
}
...
}
Hash kod qiymatlari o'zgarishi mumkin. Turning kelajakdagi versiyasi hash kodni hisoblash uchun boshqa algoritm ishlatishi mumkin. Bir kompaniya foydalanuvchi parollarining GetHashCode qiymatlarini bazaga saqlagan. CLR yangi versiyasiga yangilanganda String.GetHashCode boshqa qiymat qaytara boshlagan va hech bir foydalanuvchi tizimga kira olmay qolgan!
dynamic Primitiv Turi
C# tur-xavfsiz dasturlash tili. Bu shuni anglatadiki, barcha ifodalar biror turning instansiyasiga aylanadi va kompilyator faqat bu tur uchun to'g'ri bo'lgan amalni bajaradigan kodni yaratadi.
Biroq, dastur ish vaqtida oldindan bilmaydigan ma'lumotlar bilan ishlashi kerak bo'lgan holatlar ham mavjud. Bu hollarda C# dynamic kalit so'zini taklif qiladi:
internal static class DynamicDemo {
public static void Main() {
dynamic value;
for (Int32 demo = 0; demo < 2; demo++) {
value = (demo == 0) ? (dynamic) 5 : (dynamic) "A";
value = value + value;
M(value);
}
}
private static void M(Int32 n) { Console.WriteLine("M(Int32): " + n); }
private static void M(String s) { Console.WriteLine("M(String): " + s); }
}
// Natija:
// M(Int32): 10
// M(String): AA
Bu yerda value ning turi dynamic deb belgilangan. Birinchi iteratsiyada value 5 (Int32) ga teng bo'ladi va + operatori 10 ni hosil qiladi. Ikkinchi iteratsiyada value "A" (String) bo'ladi va + operatori "AA" ni hosil qiladi.
Kompilyator dynamic ifodaga duch kelganida, ish vaqtida bajariladigan maxsus IL kodi (payload deb ataladi) yaratadi. Ish vaqtida payload kod dynamic ifoda/o'zgaruvchi haqiqiy turiga asosan bajariladigan aniq operatsiyani aniqlaydi.
Maydon, parametr yoki metod qaytarish turi dynamic deb belgilanganda, kompilyator uni System.Object turiga aylantiradi va metadatada System.Runtime.CompilerServices.DynamicAttribute ni qo'llaydi.
var bilan lokal o'zgaruvchi e'lon qilish — kompilyatorga aniq ma'lumot turini ifodadan aniqlash imkonini beradigan sintaktik yorliq. dynamic esa boshqa narsa — ish vaqtida hal qilinadigan operatsiyalarni ifodalaydi. var faqat lokal o'zgaruvchilar uchun ishlatiladi, dynamic esa lokal o'zgaruvchilar, maydonlar va argumentlar uchun. var bilan e'lon qilingan o'zgaruvchini initsializatsiya qilish majburiy, dynamic bilan esa shart emas.
dynamic ish vaqtida COM IDispatch interfeysi bilan ishlashda juda qulay. Masalan, Excel COM obyekti bilan ishlash:
// dynamic dan OLDIN (ko'p castlar kerak)
using Microsoft.Office.Interop.Excel;
...
((Range)excel.Cells[1, 1]).Value = "Text in cell A1";
// dynamic bilan (sodda va o'qilishi oson)
excel.Cells[1, 1].Value = "Text in cell A1";
C# runtime binder Microsoft.CSharp.dll assemblydagi kodni ishlatadi. dynamic kalit so'zini qo'llaganingizda bu assembly loyihangizga havola qilinishi kerak. Ish vaqtida Microsoft.CSharp.dll, System.dll va System.Core.dll ham yuklanadi.
Dinamik baholash bilan bog'liq barcha overhead sababli, siz dynamic xususiyatidan yetarli sintaksis soddalashtirishi olayotganingizga ishonch hosil qilishingiz kerak. Agar faqat bir-ikki joyda dynamic xatti-harakatiga ehtiyoj bo'lsa, reflection (managed obyektlar uchun) yoki qo'lda casting (COM obyektlar uchun) ishlatish samaraliroq bo'lishi mumkin.
Ushbu bobda siz primitiv turlar, reference va value turlarning farqlari, boxing/unboxing mexanizmi, obyekt tengligi va identifikatsiyasi, hash kodlar va dynamic primitiv turi haqida o'rgandingiz. Bu tushunchalar .NET Framework da ishlashning muhim poydevoridir. Boxing/unboxing operatsiyalarini minimallashtirib, generic kolleksiyalardan foydalanib va value turlarni to'g'ri loyihalashtirish orqali samarali ilovalar yaratishingiz mumkin.