14-bo‘lim

Generiklar

Generik metod va sinflar, tur parametrlari, cheklovlar, generik interfeyslar va nima uchun object o'rniga generik ishlatiladi.

🕑 12 daqiqa o‘qish 📄 733 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: bir xil kod, turli turlar
  2. Generik sinf
  3. Cheklovlar
  4. Bir nechta tur parametri
  5. Generik interfeys
  6. Generik metod va tur aniqlash
  7. Amaliy misol: natija turi
  8. Xulosa

Generik - tur parametri bilan yoziladigan kod. List<string> va List<int> - bitta sinf, ikki xil tur bilan.

Bu yozilgan kodni qayta ishlatishning eng kuchli usullaridan biri.

Muammo: bir xil kod, turli turlar #

Ikkita sonni almashtiruvchi metod yozdingiz. Endi satrlar uchun ham kerak. Keyin double uchun...

CSHARP
int a = 1, b = 2;
Almashtir(ref a, ref b);
Console.WriteLine($"int: {a}, {b}");

string x = "bir", y = "ikki";
Almashtir(ref x, ref y);
Console.WriteLine($"string: {x}, {y}");

double p = 1.5, q = 2.5;
Almashtir(ref p, ref q);
Console.WriteLine($"double: {p}, {q}");

void Almashtir<T>(ref T birinchi, ref T ikkinchi)
{
    (birinchi, ikkinchi) = (ikkinchi, birinchi);
}
Natija
int: 2, 1
string: ikki, bir
double: 2.5, 1.5

Bitta metod uchala tur uchun ham ishladi. T - tur parametri, chaqirishda uni kompilyator o'zi aniqlaydi.

Nega shunchaki object ishlatmaslik kerak

Generiklardan oldin hamma narsa object orqali qilinardi. Uch muammosi bor:

  1. Turlar tekshirilmaydi. object ro'yxatiga tasodifan satr ham, son ham tushadi va kompilyator ogohlantirmaydi.
  2. Qaytarib aylantirish kerak. Har safar (int) yozish - qo'shimcha kod va potensial istisno.
  3. Qiymat turlari qutilanadi. int ni object ga aylantirish uni uyumga ko'chiradi - bu xotira va vaqt sarfi.

Generiklar uchala muammoni ham hal qiladi.

Generik sinf #

CSHARP
var sonlar = new Quti<int>();
sonlar.Qoy(42);

var matnlar = new Quti<string>();
matnlar.Qoy("Namangan");

Console.WriteLine(sonlar.Ol());
Console.WriteLine(matnlar.Ol());
Console.WriteLine($"Turlari: {sonlar.TurNomi} va {matnlar.TurNomi}");

class Quti<T>
{
    private T? ichidagi;

    public void Qoy(T narsa) => ichidagi = narsa;

    public T? Ol() => ichidagi;

    public string TurNomi => typeof(T).Name;
}
Natija
42
Namangan
Turlari: Int32 va String

Cheklovlar #

Ba'zan T haqida biror narsa bilish kerak bo'ladi. Cheklovsiz T ustida deyarli hech narsa qilib bo'lmaydi:

CSHARP
Console.WriteLine(Kattasi(5, 9));
Console.WriteLine(Kattasi("olma", "behi"));
Console.WriteLine(Kattasi(2.5, 1.5));

T Kattasi<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) >= 0 ? a : b;
}
Natija
9
olma
2.5

where T : IComparable<T> - "T solishtirilishi mumkin bo'lsin" degani. Shu tufayli CompareTo ni chaqira oldik.

CheklovMa'nosi
where T : classHavola turi bo'lsin
where T : structQiymat turi bo'lsin
where T : new()Parametrsiz konstruktori bo'lsin
where T : ShaklShu sinfdan meros olgan bo'lsin
where T : IComparable<T>Shu interfeysni amalga oshirgan bo'lsin
where T : notnullnull bo'lmasin

Bir nechta cheklovni birga yozish ham mumkin:

CSHARP
var omborxona = new Ombor<Mahsulot>();
omborxona.Yarat();
omborxona.Yarat();

Console.WriteLine($"Yaratildi: {omborxona.Soni} ta");
Console.WriteLine(omborxona.Birinchi());

class Ombor<T> where T : class, new()
{
    private readonly List<T> narsalar = [];

    public void Yarat() => narsalar.Add(new T());

    public int Soni => narsalar.Count;

    public T? Birinchi() => narsalar.Count > 0 ? narsalar[0] : null;
}

class Mahsulot
{
    public override string ToString() => "Yangi mahsulot";
}
Natija
Yaratildi: 2 ta
Yangi mahsulot

new() cheklovi tufayli sinf ichida new T() yozish mumkin bo'ldi.

Bir nechta tur parametri #

CSHARP
var juft = new Juftlik<string, int>("Namangan", 626000);
Console.WriteLine(juft);

var boshqa = new Juftlik<int, bool>(42, true);
Console.WriteLine(boshqa);

class Juftlik<TBirinchi, TIkkinchi>(TBirinchi birinchi, TIkkinchi ikkinchi)
{
    public TBirinchi Birinchi => birinchi;
    public TIkkinchi Ikkinchi => ikkinchi;

    public override string ToString() => $"({Birinchi}, {Ikkinchi})";
}
Natija
(Namangan, 626000)
(42, True)

Dictionary<TKey, TValue> aynan shu tarzda qurilgan.

Tur parametrlarini to'g'ri nomlash

An'ana: bitta bo'lsa T, bir nechta bo'lsa ma'noli nom bilan va T harfidan boshlab: TKey, TValue, TNatija.

Bu kod o'qiyotgan odamga darhol "bu tur parametri, oddiy sinf emas" deb bildiradi.

Generik interfeys #

CSHARP
var sonOmbor = new XotiraOmbori<int>();
sonOmbor.Saqla(1, 100);
sonOmbor.Saqla(2, 200);

Console.WriteLine(sonOmbor.Ol(1));
Console.WriteLine($"Yo'q kalit: {sonOmbor.Ol(99)}");
Console.WriteLine($"Jami: {sonOmbor.Soni}");

interface IOmbor<T>
{
    void Saqla(int kalit, T qiymat);
    T? Ol(int kalit);
    int Soni { get; }
}

class XotiraOmbori<T> : IOmbor<T>
{
    private readonly Dictionary<int, T> malumot = [];

    public void Saqla(int kalit, T qiymat) => malumot[kalit] = qiymat;

    public T? Ol(int kalit) =>
        malumot.TryGetValue(kalit, out T? q) ? q : default;

    public int Soni => malumot.Count;
}
Natija
100
Yo'q kalit: 0
Jami: 2
Qiymat turlarida T? - bu Nullable<T> emas

Yuqoridagi chiqishga diqqat qiling: mavjud bo'lmagan kalit uchun 0 qaytdi, null emas.

Sababi nozik: cheklovsiz generikda T? Nullable<T> degani emas. U shunchaki "bu qiymat default bo'lishi mumkin" degan izoh. T ning o'rniga int qo'yilganda T? oddiy int bo'lib qoladi - va int hech qachon null bo'la olmaydi.

Shuning uchun quyidagi kod kompilyatsiya qilinmaydi:

Natija
error CS0019: Operator '??' cannot be applied to operands
of type 'int' and 'int'

Agar null qaytarish kerak bo'lsa, tur parametrini cheklang:

CheklovT? nimani bildiradi
cheklovsizdefault(T) bo'lishi mumkin - int uchun 0
where T : classHaqiqiy null
where T : structHaqiqiy Nullable<T>, ya'ni int?

Bu C# dagi eng ko'p chalkashtiriladigan joylardan biri - jadvalni yodda tuting.

default nima qaytaradi

default - turning sukut qiymati:

Turdefault
int, double0
boolfalse
string va boshqa havola turlarinull
structBarcha maydonlari sukut qiymatda

Generik kodda null deb yozib bo'lmaydi (chunki T qiymat turi bo'lishi mumkin), shuning uchun default ishlatiladi.

Generik metod va tur aniqlash #

CSHARP
Console.WriteLine(Birlashtir(1, 2, 3));
Console.WriteLine(Birlashtir("a", "b"));
Console.WriteLine(Birlashtir<double>(1.5, 2.5));

string Birlashtir<T>(params T[] narsalar) => string.Join(" + ", narsalar);
Natija
1 + 2 + 3
a + b
1.5 + 2.5

Ko'p hollarda <T> ni yozish shart emas - kompilyator argumentlardan aniqlaydi. Oshkora yozish faqat noaniqlik bo'lganda kerak.

Amaliy misol: natija turi #

Generiklar bilan foydali naqsh - "muvaffaqiyat yoki xato" turini yaratish:

CSHARP
Natija<int> yaxshi = Bolish(10, 2);
Natija<int> yomon = Bolish(10, 0);

Console.WriteLine(yaxshi.Xabar());
Console.WriteLine(yomon.Xabar());

Natija<int> Bolish(int a, int b) =>
    b == 0
        ? Natija<int>.Xato("nolga bo'lib bo'lmaydi")
        : Natija<int>.Muvaffaqiyat(a / b);

class Natija<T>
{
    private readonly T? qiymat;
    private readonly string? xato;

    private Natija(T? qiymat, string? xato)
    {
        this.qiymat = qiymat;
        this.xato = xato;
    }

    public static Natija<T> Muvaffaqiyat(T qiymat) => new(qiymat, null);
    public static Natija<T> Xato(string sabab) => new(default, sabab);

    public string Xabar() =>
        xato is null ? $"Natija: {qiymat}" : $"Xato: {xato}";
}
Natija
Natija: 5
Xato: nolga bo'lib bo'lmaydi

Bu yondashuv istisno tashlashning muqobili: xato qaytarish qiymatining bir qismi bo'ladi va uni e'tiborsiz qoldirib bo'lmaydi.

Amaliy topshiriq
  1. Ikki qiymatni almashtiruvchi generik metod yozing.
  2. Uni uchta turli tur bilan chaqiring.
  3. Generik Quti<T> sinfini yarating.
  4. where T : IComparable<T> cheklovi bilan kattasini topuvchi metod yozing.
  5. Cheklovni olib tashlab, kompilyator nima deyishini ko'ring.
  6. where T : new() bilan obyekt yaratuvchi sinf yozing.
  7. Ikki tur parametrli Juftlik sinfini yozing.
  8. Generik interfeys e'lon qilib, uni amalga oshiring.
  9. default qaysi turlarda nima qaytarishini tekshiring.
  10. object va generik yondashuv farqini o'z so'zingiz bilan yozing.

Xulosa #

  • Generik - tur parametri bilan yoziladigan kod; bitta metod ko'p tur uchun ishlaydi.
  • object o'rniga generik: turlar tekshiriladi, aylantirish kerak emas, qutilash yo'q.
  • Cheklovlar (where) T haqida nima bilishni belgilaydi.
  • where T : new() - sinf ichida new T() yozishga ruxsat beradi.
  • Bir nechta tur parametri TKey, TValue kabi nomlanadi.
  • Generik kodda null emas, default ishlatiladi.
  • Ko'pincha <T> ni yozish shart emas - kompilyator o'zi aniqlaydi.

Keyingi bo'limda LINQ ni - C# ning eng kuchli vositalaridan birini o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.