20-bo‘lim

Amaliy loyiha - talabalar jurnali

Barcha o'rganilganni bitta dasturda birlashtiramiz: model, ombor interfeysi, JSON saqlash, LINQ hisobotlar, istisnolar va testlar.

🕑 13 daqiqa o‘qish 📄 780 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Loyiha tuzilmasi
  2. 1-qadam. Ma'lumot modeli
  3. 2-qadam. Ombor interfeysi
  4. 3-qadam. Hisobot xizmati
  5. Har bir talabaning o'rtachasi
  6. Guruhlar bo'yicha
  7. Fanlar bo'yicha
  8. 4-qadam. JSON ga saqlash
  9. 5-qadam. Xatolarni boshqarish
  10. 6-qadam. Testlar
  11. 7-qadam. To'liq dastur
  12. Nimalar ishlatildi
  13. Keyingi qadamlar
  14. Xulosa

Oxirgi bo'lim. Yangi mavzu yo'q - o'rgangan hamma narsangizni bitta ishlaydigan dasturda birlashtiramiz.

Vazifa: talabalar jurnali. Dastur talabalar va baholarni saqlaydi, fayldan o'qiydi, hisobot chiqaradi va o'zini sinovdan o'tkazadi.

Loyiha tuzilmasi #

Haqiqiy loyihada har bir sinf o'z faylida turadi:

Natija
Jurnal/
   Program.cs           <- kirish nuqtasi
   Modellar/
      Talaba.cs
      Baho.cs
      Daraja.cs
   Ombor/
      IJurnalOmbori.cs
      JsonOmbor.cs
   Xizmatlar/
      JurnalXizmati.cs
   Jurnal.csproj

Papkalar nom fazolariga mos kelishi qabul qilingan an'ana. Bu darslikda misollar qisqa bo'lishi uchun hammasi bitta faylda ko'rsatiladi.

1-qadam. Ma'lumot modeli #

Ma'lumot tashiydigan turlar uchun record (11-bo'lim):

CSHARP
var t = new Talaba(1, "Husanboy Qodirov", "A");
var b = new Baho(1, "Matematika", 92);

Console.WriteLine(t);
Console.WriteLine(b);
Console.WriteLine($"Daraja: {DarajaAniqla(92)}");
Console.WriteLine($"Daraja: {DarajaAniqla(68.5)}");

Daraja DarajaAniqla(double ball) => ball switch
{
    >= 90 => Daraja.Alo,
    >= 75 => Daraja.Yaxshi,
    >= 60 => Daraja.Qoniqarli,
    _ => Daraja.Qoniqarsiz
};

record Talaba(int Id, string Ism, string Guruh);

record Baho(int TalabaId, string Fan, int Ball);

enum Daraja { Qoniqarsiz, Qoniqarli, Yaxshi, Alo }
Natija
Talaba { Id = 1, Ism = Husanboy Qodirov, Guruh = A }
Baho { TalabaId = 1, Fan = Matematika, Ball = 92 }
Daraja: Alo
Daraja: Qoniqarli

record bepul ToString() berdi, switch ifodasi esa darajani aniqladi (5-bo'lim).

2-qadam. Ombor interfeysi #

Ma'lumotni qayerda saqlashni interfeys orqali ajratamiz (10-bo'lim). Shunda keyin JSON o'rniga bazaga o'tish oson bo'ladi:

CSHARP
IJurnalOmbori ombor = new XotiraOmbori();

ombor.TalabaQosh(new Talaba(1, "Husanboy", "A"));
ombor.TalabaQosh(new Talaba(2, "Malika", "B"));
ombor.BahoQosh(new Baho(1, "Matematika", 92));

Console.WriteLine($"Talabalar: {ombor.Talabalar().Count}");
Console.WriteLine($"Baholar:   {ombor.Baholar().Count}");

interface IJurnalOmbori
{
    void TalabaQosh(Talaba talaba);
    void BahoQosh(Baho baho);
    IReadOnlyList<Talaba> Talabalar();
    IReadOnlyList<Baho> Baholar();
}

class XotiraOmbori : IJurnalOmbori
{
    private readonly List<Talaba> talabalar = [];
    private readonly List<Baho> baholar = [];

    public void TalabaQosh(Talaba talaba)
    {
        if (talabalar.Any(t => t.Id == talaba.Id))
        {
            throw new ArgumentException(
                $"{talaba.Id} raqamli talaba allaqachon bor");
        }

        talabalar.Add(talaba);
    }

    public void BahoQosh(Baho baho) => baholar.Add(baho);

    public IReadOnlyList<Talaba> Talabalar() => talabalar;
    public IReadOnlyList<Baho> Baholar() => baholar;
}

record Talaba(int Id, string Ism, string Guruh);
record Baho(int TalabaId, string Fan, int Ball);
Natija
Talabalar: 2
Baholar:   1
IReadOnlyList nega ishlatildi?

Agar List<Talaba> qaytarsak, chaqiruvchi kod unga to'g'ridan-to'g'ri element qo'shishi mumkin - ya'ni TalabaQosh dagi tekshiruvni chetlab o'tadi.

IReadOnlyList<T> esa faqat o'qishga ruxsat beradi. Ma'lumot bir xil, lekin uni o'zgartirish yo'li faqat sinf metodlari orqali qoladi.

Bu 9-bo'limdagi inkapsulyatsiya qoidasining amaliy ko'rinishi.

3-qadam. Hisobot xizmati #

Hisoblashlar alohida sinfda - LINQ bilan (15-bo'lim):

CSHARP
(int Id, string Ism, string Guruh)[] talabalar =
[
    (1, "Husanboy Qodirov", "A"),
    (2, "Malika Tursunova", "B"),
    (3, "Nodira Saidova", "A"),
    (4, "Aziza Rahimova", "B")
];

(int TalabaId, string Fan, int Ball)[] baholar =
[
    (1, "Matematika", 92), (1, "Fizika", 85),
    (2, "Matematika", 78), (2, "Fizika", 88),
    (3, "Matematika", 65), (3, "Fizika", 72),
    (4, "Matematika", 95), (4, "Fizika", 91)
];

Shu ma'lumot bilan ishlaymiz. Bu yerda record o'rniga kortej ishlatildi - hisobot misollarini qisqa qilish uchun. To'liq dasturda (7-qadam) yana record ga qaytamiz.

Har bir talabaning o'rtachasi #

CSHARP
using System.Globalization;

var natijalar = talabalar
    .Select(t => new
    {
        t.Ism,
        t.Guruh,
        Ortacha = baholar
            .Where(b => b.TalabaId == t.Id)
            .Average(b => b.Ball)
    })
    .OrderByDescending(x => x.Ortacha);

foreach (var n in natijalar)
{
    Console.WriteLine(string.Format(CultureInfo.InvariantCulture,
        "{0,-18} {1}  {2,5:F1}", n.Ism, n.Guruh, n.Ortacha));
}
Natija
Aziza Rahimova     B   93.0
Husanboy Qodirov   A   88.5
Malika Tursunova   B   83.0
Nodira Saidova     A   68.5

Select ichida nomsiz tur yaratildi - bu vaqtinchalik natijalar uchun qulay.

Guruhlar bo'yicha #

CSHARP
using System.Globalization;

var guruhlar = talabalar
    .GroupBy(t => t.Guruh)
    .Select(g => new
    {
        Guruh = g.Key,
        Soni = g.Count(),
        Ortacha = g.Average(t => baholar
            .Where(b => b.TalabaId == t.Id)
            .Average(b => b.Ball))
    })
    .OrderBy(x => x.Guruh);

foreach (var g in guruhlar)
{
    Console.WriteLine(string.Format(CultureInfo.InvariantCulture,
        "{0} guruhi: {1} talaba, o'rtacha {2:F1}",
        g.Guruh, g.Soni, g.Ortacha));
}
Natija
A guruhi: 2 talaba, o'rtacha 78.5
B guruhi: 2 talaba, o'rtacha 88.0

Fanlar bo'yicha #

CSHARP
using System.Globalization;

var fanlar = baholar
    .GroupBy(b => b.Fan)
    .Select(g => new
    {
        Fan = g.Key,
        Ortacha = g.Average(b => b.Ball),
        Eng = g.Max(b => b.Ball)
    })
    .OrderByDescending(x => x.Ortacha);

foreach (var f in fanlar)
{
    Console.WriteLine(string.Format(CultureInfo.InvariantCulture,
        "{0,-12} o'rtacha {1:F1}, eng yuqori {2}", f.Fan, f.Ortacha, f.Eng));
}
Natija
Fizika       o'rtacha 84.0, eng yuqori 91
Matematika   o'rtacha 82.5, eng yuqori 95

4-qadam. JSON ga saqlash #

Ombor interfeysining fayl bilan ishlaydigan varianti (17-bo'lim):

CSHARP
using System.Text.Json;

string yol = Path.Combine(Path.GetTempPath(), "jurnal-sinov.json");

var ombor = new JsonOmbor(yol);
ombor.TalabaQosh(new Talaba(1, "Husanboy", "A"));
ombor.TalabaQosh(new Talaba(2, "Malika", "B"));
ombor.Saqla();

var ikkinchi = new JsonOmbor(yol);
ikkinchi.Yukla();

Console.WriteLine($"Fayldan o'qildi: {ikkinchi.Talabalar().Count} ta talaba");
foreach (var t in ikkinchi.Talabalar())
{
    Console.WriteLine($"  {t.Id}. {t.Ism} ({t.Guruh})");
}

File.Delete(yol);

class JsonOmbor(string yol)
{
    private List<Talaba> talabalar = [];

    public void TalabaQosh(Talaba talaba) => talabalar.Add(talaba);

    public IReadOnlyList<Talaba> Talabalar() => talabalar;

    public void Saqla() =>
        File.WriteAllText(yol, JsonSerializer.Serialize(talabalar));

    public void Yukla()
    {
        if (!File.Exists(yol))
        {
            talabalar = [];
            return;
        }

        string json = File.ReadAllText(yol);
        talabalar = JsonSerializer.Deserialize<List<Talaba>>(json) ?? [];
    }
}

record Talaba(int Id, string Ism, string Guruh);
Natija
Fayldan o'qildi: 2 ta talaba
  1. Husanboy (A)
  2. Malika (B)
Yukla da fayl yo'qligini tekshirish

Diqqat qiling: Yukla fayl mavjudligini tekshiradi va yo'q bo'lsa bo'sh ro'yxat qo'yadi.

Bu 12-bo'limdagi qoidaning qo'llanishi: null emas, bo'sh to'plam qaytaring. Dastur birinchi marta ishga tushganda fayl hali yo'q - bu xato emas, odatiy holat.

?? [] qismi ham shu maqsadda: Deserialize null qaytarishi mumkin (masalan fayl ichida null so'zi bo'lsa).

5-qadam. Xatolarni boshqarish #

CSHARP
var ombor = new Ombor();

Qosh(ombor, new Talaba(1, "Husanboy", "A"));
Qosh(ombor, new Talaba(1, "Boshqa odam", "B"));
Qosh(ombor, new Talaba(2, "", "A"));
Qosh(ombor, new Talaba(3, "Malika", "B"));

Console.WriteLine($"\nNatijada {ombor.Soni} ta talaba qo'shildi");

void Qosh(Ombor o, Talaba t)
{
    try
    {
        o.Qosh(t);
        Console.WriteLine($"OK:   {t.Id} - {t.Ism}");
    }
    catch (ArgumentException x)
    {
        Console.WriteLine($"XATO: {x.Message}");
    }
}

class Ombor
{
    private readonly List<Talaba> talabalar = [];

    public int Soni => talabalar.Count;

    public void Qosh(Talaba talaba)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(talaba.Ism, nameof(talaba));

        if (talabalar.Any(t => t.Id == talaba.Id))
        {
            throw new ArgumentException($"{talaba.Id} raqami band");
        }

        talabalar.Add(talaba);
    }
}

record Talaba(int Id, string Ism, string Guruh);
Natija
OK:   1 - Husanboy
XATO: 1 raqami band
XATO: The value cannot be an empty string or composed entirely of whitespace. (Parameter 'talaba')
OK:   3 - Malika

Natijada 2 ta talaba qo'shildi

Xato bo'lgan yozuvlar o'tkazib yuborildi, dastur esa to'xtamadi (13-bo'lim).

6-qadam. Testlar #

Endi mantiqni sinovdan o'tkazamiz (19-bo'lim):

CSHARP
Tekshir.Teng(Daraja.Alo, DarajaAniqla(95), "95 ball");
Tekshir.Teng(Daraja.Alo, DarajaAniqla(90), "aynan 90 - chegara");
Tekshir.Teng(Daraja.Yaxshi, DarajaAniqla(89.9), "chegaradan bir oz past");
Tekshir.Teng(Daraja.Yaxshi, DarajaAniqla(75), "aynan 75");
Tekshir.Teng(Daraja.Qoniqarli, DarajaAniqla(60), "aynan 60");
Tekshir.Teng(Daraja.Qoniqarsiz, DarajaAniqla(59.9), "60 dan past");
Tekshir.Teng(Daraja.Qoniqarsiz, DarajaAniqla(0), "nol ball");

Tekshir.Yakun();

Daraja DarajaAniqla(double ball) => ball switch
{
    >= 90 => Daraja.Alo,
    >= 75 => Daraja.Yaxshi,
    >= 60 => Daraja.Qoniqarli,
    _ => Daraja.Qoniqarsiz
};

enum Daraja { Qoniqarsiz, Qoniqarli, Yaxshi, Alo }

static class Tekshir
{
    private static int otdi, yiqildi;

    public static void Teng<T>(T kutilgan, T haqiqiy, string nom)
    {
        if (Equals(kutilgan, haqiqiy))
        {
            otdi++;
            Console.WriteLine($"  OK    {nom}");
        }
        else
        {
            yiqildi++;
            Console.WriteLine($"  XATO  {nom}: {kutilgan} != {haqiqiy}");
        }
    }

    public static void Yakun() =>
        Console.WriteLine($"\nO'tdi: {otdi}, yiqildi: {yiqildi}");
}
Natija
  OK    95 ball
  OK    aynan 90 - chegara
  OK    chegaradan bir oz past
  OK    aynan 75
  OK    aynan 60
  OK    60 dan past
  OK    nol ball

O'tdi: 7, yiqildi: 0

Diqqat: testlarning yarmi chegaraviy qiymatlar - aynan 90, 75, 60 va ulardan bir oz past. Xato odatda shu yerda bo'ladi.

7-qadam. To'liq dastur #

Hammasini birlashtiramiz:

CSHARP
using System.Globalization;
using System.Text.Json;

string yol = Path.Combine(Path.GetTempPath(), "jurnal-yakuniy.json");
var inv = CultureInfo.InvariantCulture;

// 1. Ma'lumot tayyorlash va saqlash
var jurnal = new Jurnal();
jurnal.TalabaQosh(new Talaba(1, "Husanboy Qodirov", "A"));
jurnal.TalabaQosh(new Talaba(2, "Malika Tursunova", "B"));
jurnal.TalabaQosh(new Talaba(3, "Nodira Saidova", "A"));
jurnal.TalabaQosh(new Talaba(4, "Aziza Rahimova", "B"));

int[,] ballar = { { 92, 85 }, { 78, 88 }, { 65, 72 }, { 95, 91 } };
for (int i = 0; i < 4; i++)
{
    jurnal.BahoQosh(new Baho(i + 1, "Matematika", ballar[i, 0]));
    jurnal.BahoQosh(new Baho(i + 1, "Fizika", ballar[i, 1]));
}

File.WriteAllText(yol, jurnal.Json());

// 2. Fayldan qayta o'qish
var oqilgan = Jurnal.JsonDan(File.ReadAllText(yol));

Console.WriteLine("=== TALABALAR JURNALI ===\n");

Console.WriteLine("Reyting:");
foreach (var n in oqilgan.Reyting())
{
    Console.WriteLine(string.Format(inv, "  {0,-18} {1}  {2,5:F1}  {3}",
        n.Ism, n.Guruh, n.Ortacha, n.Daraja));
}

Console.WriteLine("\nGuruhlar:");
foreach (var g in oqilgan.GuruhlarBoyicha())
{
    Console.WriteLine(string.Format(inv, "  {0}: {1} talaba, o'rtacha {2:F1}",
        g.Guruh, g.Soni, g.Ortacha));
}

Console.WriteLine(string.Format(inv,
    "\nUmumiy o'rtacha: {0:F2}", oqilgan.UmumiyOrtacha()));
int alochilar = oqilgan.Reyting().Count(r => r.Daraja == Daraja.Alo);
Console.WriteLine($"A'lochilar soni: {alochilar}");

File.Delete(yol);

record Talaba(int Id, string Ism, string Guruh);
record Baho(int TalabaId, string Fan, int Ball);
record Yozuv(string Ism, string Guruh, double Ortacha, Daraja Daraja);
enum Daraja { Qoniqarsiz, Qoniqarli, Yaxshi, Alo }

record JurnalHolati(List<Talaba> Talabalar, List<Baho> Baholar);

class Jurnal
{
    private readonly List<Talaba> talabalar = [];
    private readonly List<Baho> baholar = [];

    public void TalabaQosh(Talaba t)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(t.Ism, nameof(t));

        if (talabalar.Any(x => x.Id == t.Id))
        {
            throw new ArgumentException($"{t.Id} raqami band");
        }

        talabalar.Add(t);
    }

    public void BahoQosh(Baho b) => baholar.Add(b);

    public string Json() =>
        JsonSerializer.Serialize(new JurnalHolati(talabalar, baholar));

    public static Jurnal JsonDan(string json)
    {
        var holat = JsonSerializer.Deserialize<JurnalHolati>(json)
                    ?? new JurnalHolati([], []);

        var j = new Jurnal();
        foreach (var t in holat.Talabalar) j.TalabaQosh(t);
        foreach (var b in holat.Baholar) j.BahoQosh(b);
        return j;
    }

    public List<Yozuv> Reyting() =>
        talabalar
            .Select(t =>
            {
                double ortacha = baholar
                    .Where(b => b.TalabaId == t.Id)
                    .Average(b => b.Ball);
                return new Yozuv(t.Ism, t.Guruh, ortacha, Daraja(ortacha));
            })
            .OrderByDescending(y => y.Ortacha)
            .ToList();

    public List<(string Guruh, int Soni, double Ortacha)> GuruhlarBoyicha() =>
        Reyting()
            .GroupBy(y => y.Guruh)
            .Select(g => (g.Key, g.Count(), g.Average(y => y.Ortacha)))
            .OrderBy(x => x.Key)
            .ToList();

    public double UmumiyOrtacha() => baholar.Average(b => b.Ball);

    private static Daraja Daraja(double ball) => ball switch
    {
        >= 90 => global::Daraja.Alo,
        >= 75 => global::Daraja.Yaxshi,
        >= 60 => global::Daraja.Qoniqarli,
        _ => global::Daraja.Qoniqarsiz
    };
}
Natija
=== TALABALAR JURNALI ===

Reyting:
  Aziza Rahimova     B   93.0  Alo
  Husanboy Qodirov   A   88.5  Yaxshi
  Malika Tursunova   B   83.0  Yaxshi
  Nodira Saidova     A   68.5  Qoniqarli

Guruhlar:
  A: 2 talaba, o'rtacha 78.5
  B: 2 talaba, o'rtacha 88.0

Umumiy o'rtacha: 83.25
A'lochilar soni: 1
global:: nima uchun kerak bo'ldi?

Jurnal sinfida Daraja degan metod bor va Daraja degan enum ham bor. Sinf ichida Daraja.Alo deb yozsangiz, kompilyator metodni ko'radi va xato beradi.

global::Daraja - "eng yuqori darajadagi Daraja ni nazarda tutyapman" degani.

Amalda bunday to'qnashuvdan qochish kerak: metodni DarajaAniqla deb nomlansa, muammo umuman bo'lmasdi. Bu yerda u ataylab qoldirildi, chunki haqiqiy loyihada shunday holat uchraydi va yechimini bilish foydali.

Nimalar ishlatildi #

Bo'limLoyihada qayerda
3decimal va double tanlovi, InvariantCulture
5switch ifodasi bilan daraja aniqlash
7List<T>, Dictionary
9Inkapsulyatsiya, IReadOnlyList
10IJurnalOmbori interfeysi
11record va enum
12?? [] va bo'sh to'plam qaytarish
13ArgumentException, try/catch
15Select, GroupBy, OrderBy, Average
17JSON va fayl
19Chegaraviy testlar

Keyingi qadamlar #

Loyihani o'zingiz kengaytiring

Eng yaxshi mashq - shu dasturga yangi imkoniyat qo'shish:

  1. Talabani o'chirish va tahrirlash;
  2. Chorak tushunchasi - baholarni vaqt bo'yicha ajratish;
  3. Konsol menyusi - while (true) va switch bilan;
  4. CSV ga eksport - Excel'da ochish uchun;
  5. IJurnalOmbori ning SQLite varianti - bu saytdagi SQL va MySQL darsligi bilan birga.

Har bir qo'shimcha uchun avval test yozing, keyin kodni.

Bundan keyin nima o'rganish

C# asoslari tugadi. Yo'nalishlar:

  • Veb - ASP.NET Core; bu saytdagi HTML va CSS hamda JavaScript darsliklari bilan birga to'liq veb dasturchi bo'lasiz;
  • Ma'lumotlar bazasi - Entity Framework Core; avval SQL va MySQL darsligini o'ting;
  • O'yin - Unity; C# bilimingiz to'g'ridan-to'g'ri ishlaydi;
  • Arxitektura - Dizayn naqshlari va Dasturiy ta'minot muhandisligi darsliklari;
  • Algoritmlar - Algoritmlar va ma'lumotlar tuzilmasi darsligi; suhbatlarda eng ko'p so'raladigan mavzu.
Yakuniy amaliy topshiriq
  1. dotnet new console -o Jurnal bilan loyiha yarating.
  2. Talaba, Baho record'lari va Daraja enum'ini yozing.
  3. IJurnalOmbori interfeysini va uning xotira variantini yozing.
  4. Bir xil Id qo'shishga urinishda istisno tashlang.
  5. LINQ bilan reyting hisobotini chiqaring.
  6. Guruhlar va fanlar bo'yicha hisobot qo'shing.
  7. Ma'lumotni JSON ga saqlang va qayta o'qing.
  8. DarajaAniqla uchun chegaraviy testlar yozing.
  9. Barcha sonlarni InvariantCulture bilan chiqaring.
  10. Loyihaga menyu qo'shib, uni to'liq dasturga aylantiring.

Xulosa #

  • Ma'lumot turlari uchun record, mantiq uchun class.
  • Saqlash usulini interfeys ortiga yashiring - keyin o'zgartirish oson.
  • Tashqariga IReadOnlyList qaytaring, List emas.
  • Hisobotlarni LINQ bilan yozing - sikl deyarli kerak emas.
  • Sonlarni InvariantCulture bilan chiqaring.
  • Xatolarni kelib chiqish joyida tekshiring (ThrowIfNullOrWhiteSpace).
  • Fayl yo'q bo'lishi xato emas - bo'sh to'plam qaytaring.
  • Testlarni chegaraviy qiymatlarga yo'naltiring.

Darslik tugadi. Endi sizda ishlaydigan C# dasturi va uni kengaytirish uchun yetarli bilim bor. Omad!

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.