Grafikus C# – Windows Forms tanfolyam › 12.3 lecke

Windows Forms · 12.3 lecke · osztály-tagok

Mezők, propertyk és metódusok

Egy osztály három építőkőből áll: mezőkből (az adatok), tulajdonságokból (a kontrollált hozzáférés az adatokhoz) és metódusokból (a műveletek). A Windows Forms vezérlői is ezekből épülnek: a Text, Width, Enabled tulajdonságok, a Show(), Close() metódusok. Ebben a leckében megtanulod, hogyan tervezz jó osztályt: az adatok legyenek private mezőkben, a külvilág tulajdonságokon és metódusokon át érje el őket, a hibás érték be se kerüljön az objektumba, és a belső gyűjteményt ne lehessen kívülről összekuszálni.

  • kb. 100 perc
  • közepes szint
  • tagok tervezése
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • megkülönbözteted a mezőt, a tulajdonságot és a metódust, és tudod, mikor melyiket választod;
  • automatikus, ellenőrző, számított, csak olvasható tulajdonságot írsz;
  • a setterben ellenőrzöd az értéket (ArgumentException), így az objektum mindig érvényes marad;
  • a belső listát csak olvashatóként adod ki (IReadOnlyList<T>);
  • metódusokat tervezel (paraméterek, visszatérési érték, túlterhelés, opcionális paraméter);
  • felismered a tipikus tervezési hibákat (nyilvános mezők, kívülről módosítható lista).

Mielőtt elkezded

Ehhez a leckéhez a 12.1–12.2 lecke és a 4.7 lecke (kivételek) szükséges.

12.2 partial class →

Mező és tulajdonság

Tanulo.csC#
public class Tanulo
{
// MEZŐ: az adat tárolója; általában private
private string nev;

// PROPERTY: kontrollált hozzáférés (get = olvasás, set = írás)
public string Nev
{
get { return nev; }
set { nev = value; } // a value a beírt érték
}

// AUTOMATIKUS property: ha nincs extra logika, a fordító létrehozza a rejtett mezőt
public string Osztaly { get; set; } = "10.A";

// kívülről csak olvasható, belül írható
public DateTime Felvetel { get; private set; } = DateTime.Now;
}

// használat: a property úgy néz ki, mint egy mező
Tanulo t = new Tanulo();
t.Nev = "Kiss Anna";
lblNev.Text = t.Nev;
// t.Felvetel = DateTime.Now; // FORDÍTÁSI HIBA: a set private
MezőTulajdonság (property)
Mi ez?Az objektum adatának tárolóhelye.Hozzáférési pont: get és/vagy set kóddal.
LáthatóságÁltalában private.Általában public.
EllenőrzésNincs: bármi beírható.A set ellenőrizheti az értéket.
Írásvédelemreadonly (csak konstruktorban).Nincs set, vagy private set.
Designer, kötésNem használható adatkötéshez.A Properties ablak és az adatkötés property-ket lát.
Miért ne legyen nyilvános a mező?Egy public string nev; mezőbe bárki bármit beírhat (üres szöveget, null értéket), és később nem tudod ellenőrzéssel védeni anélkül, hogy minden hívót átírnál. A property később kiegészíthető logikával, a hívó kódja változatlan marad.
Automatikus propertyA { get; set; } rövidítés a leggyakoribb: a fordító létrehozza a rejtett mezőt. Akkor írj teljes változatot, ha a get vagy set logikát kap.
private setA private set azt jelenti: kívülről csak olvasható, az osztályon belül módosítható. Azonosítóknak, létrehozási időnek kiváló.
A Forms is ilyenA button.Text = "OK" egy tulajdonság set ágát hívja: a vezérlő közben újrarajzolja magát. A property mögött tehát lehet kód: ezért nem mindegy, mit teszel bele.

Jósolj!

A Tanulo.Felvetel tulajdonságnak private set van. A TanuloForm kódja t.Felvetel = DateTime.Now; sort tartalmaz. Mikor derül ki a hiba?

Jóslat ellenőrzése

Fordításkor (CS0272: a set elérhetetlen): még a program futtatása előtt. Az osztály védi saját adatát: a fordító kikényszeríti a szabályt.

Ellenőrzés a setterben, számított tulajdonságok

Tanulo.cs (using System.Collections.Generic; using System.Linq;)C#
public class Tanulo
{
private string nev = "";
private readonly List<int> jegyek = new List<int>(); // belső gyűjtemény: private

// ELLENŐRZŐ property: az objektum mindig érvényes marad
public string Nev
{
get { return nev; }
set
{
if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("A név nem lehet üres."); }

nev = value.Trim();
}
}

// SZÁMÍTOTT, csak olvasható property: nincs saját tárolója, mindig a jegyekből számolódik
public double Atlag { get { return jegyek.Count == 0 ? 0 : jegyek.Average(); } }
public int JegyekSzama => jegyek.Count; // kifejezés-törzsű rövidítés

// a gyűjtemény csak olvasható nézete: nem lehet kívülről Add/Remove
public IReadOnlyList<int> Jegyek { get { return jegyek; } }

// a módosítás metóduson át, ellenőrzéssel
public void JegyetBeir(int jegy)
{
if (jegy < 1 || jegy > 5) { throw new ArgumentOutOfRangeException("jegy", "A jegy 1 és 5 közötti szám lehet."); }

jegyek.Add(jegy);
}
}

// a hívó oldal: a felület kapja el a kivételt, és közli a felhasználóval
try { tanulo.JegyetBeir((int)nudJegy.Value); }
catch (ArgumentException ex) { tsslUzenet.Text = ex.Message; }
Érvényes állapotAz osztály felelőssége, hogy az objektum sosem kerüljön érvénytelen állapotba: egy 7-es jegy vagy üres név be se léphet. A kívülről jövő adatot a belépési ponton (setter, metódus) ellenőrizd.
Számított propertyAz Atlag nincs eltárolva: mindig a jegyekből jön. Így sosem lesz elavult (nem kell a jegy hozzáadásakor frissíteni). Olcsó számításhoz property, drága számításhoz metódus (AtlagSzamol()) való.
Gyűjtemény kiadásaHa a List<int>-et public adod ki, a hívó a metódus megkerülésével bármit beletehet (t.Jegyek.Add(9)). Az IReadOnlyList<int> nézetben csak olvasható a lista. (Az alaplista tehát nem readonly tetszőlegesen: a mező readonly csak azt jelenti, hogy másik listára nem cserélhető.)
Kivétel vagy visszatérési érték?Programozói hibát (rossz paraméter) kivétellel jelzünk; a felhasználói bevitel ellenőrzését a felület is megteheti (TryParse, NumericUpDown tartománya), de az osztály soha ne bízzon a hívó előzetes ellenőrzésében.

Jósolj!

A Jegyek tulajdonság List<int> típusú és public. A hívó t.Jegyek.Add(9); sort ír. Mi lesz az Atlag értéke, és miért probléma?

Jóslat ellenőrzése

Az átlagba beleszámít a 9-es jegy: az ellenőrzést megkerülték, az objektum érvénytelen állapotba került. Az IReadOnlyList<int> kiadása és a JegyetBeir metódus ezt megakadályozza.

Metódusok tervezése

Tanulo.csC#
// művelet, amely változtat az állapoton: igen/nem eredménnyel
public bool TorolUtolsoJegy()
{
if (jegyek.Count == 0) { return false; }

jegyek.RemoveAt(jegyek.Count - 1);

return true;
}

// kérdés az objektumról (nem változtat): visszatérési érték
public int Legjobb() { return jegyek.Count == 0 ? 0 : jegyek.Max(); }

// TÚLTERHELÉS: ugyanaz a név, más paraméterek
public void JegyetBeir(int jegy) { /* ... */ }
public void JegyetBeir(params int[] jegyek) { foreach (int j in jegyek) { JegyetBeir(j); } }

// OPCIONÁLIS paraméter
public string Osszefoglalo(bool reszletes = false)
{
string s = nev + ": átlag " + Atlag.ToString("0.00");

return reszletes ? s + " (jegyek: " + string.Join(", ", jegyek) + ")" : s;
}

// ToString felülírása: a ListBox ezt jeleníti meg
public override string ToString() { return Osszefoglalo(); }
Mondd meg, ne kérdezdNe kérd el az adatokat, hogy a hívó számoljon vele: kérd meg az objektumot, hogy csinálja meg (t.Osszefoglalo()). Ha a Form kódjában jegyek.Sum() / jegyek.Count szerepel, a logika rossz helyen van.
Property vagy metódus?Property: adatszerű, olcsó, mellékhatás nélküli (Atlag, Nev). Metódus: művelet, számítás vagy mellékhatás (JegyetBeir, FajlbaMent).
TúlterhelésUgyanaz a név, más paraméterlista: a fordító a hívás alapján választ. Opcionális paraméter (= false) egyszerűsíti a gyakori hívást.
ToStringA ToString() felülírása adja meg, hogyan jelenjen meg az objektum szövegként (ListBox, MessageBox). A lst.Items.Add(tanulo) így kiírja az összefoglalót.

Jósolj!

A lstTanulok.Items.Add(tanulo) hívás a ToString felülírása nélkül mit jelenít meg a listában?

Jóslat ellenőrzése

A típus teljes nevét (például NaploProjekt.Tanulo): az alap ToString() csak ennyit tud. A felülírás adja a hasznos szöveget.

📷 Képernyőkép helye

Mit tegyél ide: a tanulói napló: a tanulók listája, a jegybeírás és a hibaüzenet érvénytelen jegynél.Javasolt fájlnév: winforms-12-3-tanulo.webp · Alt szöveg: A tanulói napló program

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. StackOverflowException a property-ben

public string Nev { get { return Nev; } set { Nev = value; } }
Hiba és javítás

A property önmagát hívja (végtelen rekurzió). A mögötte álló mezőt kell használni (nev), vagy automatikus property.

2. Az átlag nulla elemnél hibát ad

public double Atlag { get { return jegyek.Average(); } }
Hiba és javítás

Üres gyűjteményen az Average() InvalidOperationException. Ellenőrizd a Count == 0 esetet.

3. Nyilvános lista, kerülő út

public List<int> Jegyek = new List<int>();
Hiba és javítás

A hívó az ellenőrzés megkerülésével ír a listába. Private mező, IReadOnlyList property, JegyetBeir metódus.

4. A name és a Name összekeveredik

A setter a Nev (property) helyett a nev mezőt ellenőrizné, de elírod.

Ok és javítás

A mező (kis betű) és a property (nagy betű) különbsége csak a kezdőbetű: figyelj, és használd a Visual Studio Quick Actions (Ctrl+.) automatikus property-generálását.

5. A szóköz-név átmegy

if (value == "") { throw new ArgumentException(); }   // a "   " és a null átmegy
Hiba és javítás

string.IsNullOrWhiteSpace(value) kezeli a null, üres és szóközös esetet is.

6. Az Atlag nem frissül

Az Atlag mezőként tárolt, és a jegy beírása után nem számolódik újra.

Ok és javítás

Eltárolt, származtatott adat elavul. Számított property: mindig a jegyekből jön, nincs mit frissíteni.

Mini projekt: tanulói napló osztályokkal

Készíts naplót, amelyben az adatok és a szabályok a saját osztályban élnek, a Form csak megjeleníti:

  1. Tanulo osztály: Nev (ellenőrzött), Osztaly (automatikus), Felvetel (private set), privát jegylista; Jegyek (IReadOnlyList), Atlag, JegyekSzama (számított); JegyetBeir (1–5, kivétel), TorolUtolsoJegy, Legjobb, Osszefoglalo(bool), ToString.
  2. Osztaly (napló) osztály: privát List<Tanulo>; Hozzaad(Tanulo) (azonos nevű nem vehető fel újra), Torol(Tanulo), Tanulok (IReadOnlyList), OsztalyAtlag, Legjobb() (a legjobb átlagú tanuló, vagy null).
  3. Felület: txtNev, Felvesz; lstTanulok (a ToString jeleníti meg); nudJegy (1–5), Jegy beírása, Utolsó jegy törlése, Törlés; lblOsztalyAtlag, lblLegjobb; állapotsor.
  4. Hibakezelés: az osztály kivételeit a felület catch-eli és az állapotsorban közli; semmi kivétel nem omlaszt; nincs kijelölt tanuló: érthető üzenet.
  5. Frissítés: minden módosítás után egyetlen Frissit() metódus tölti újra a listát és a címkéket; a kijelölés megmarad.
  6. Tesztelés: üres név, szóközös név, ismétlődő név, 0 és 6 jegy, üres tanuló átlaga, törlés jegyek nélkül, t.Jegyek.Add próbálkozás (nem fordul), osztályátlag 0 tanulónál.
Megoldás megnyitása (próbáld ki előbb önállóan!)
Naplo.cs és Form1.cs (using System.Collections.Generic; using System.Linq;)C#
public class Naplo
{
private readonly List<Tanulo> tanulok = new List<Tanulo>();

public IReadOnlyList<Tanulo> Tanulok { get { return tanulok; } }

public double OsztalyAtlag
{
get { var van = tanulok.Where(t => t.JegyekSzama > 0).ToList(); return van.Count == 0 ? 0 : van.Average(t => t.Atlag); }
}

public void Hozzaad(Tanulo t)
{
if (t == null) { throw new ArgumentNullException("t"); }
if (tanulok.Any(x => string.Equals(x.Nev, t.Nev, StringComparison.CurrentCultureIgnoreCase))) { throw new ArgumentException("Már van ilyen nevű tanuló."); }

tanulok.Add(t);
}

public bool Torol(Tanulo t) { return tanulok.Remove(t); }

public Tanulo Legjobb()
{
return tanulok.Where(t => t.JegyekSzama > 0).OrderByDescending(t => t.Atlag).FirstOrDefault();
}
}

// ---- Form1.cs ----
private readonly Naplo naplo = new Naplo();

private void btnFelvesz_Click(object sender, EventArgs e)
{
try
{
Tanulo t = new Tanulo();

t.Nev = txtNev.Text; // az osztály ellenőriz
naplo.Hozzaad(t);
txtNev.Clear();
Frissit(t);
}
catch (ArgumentException ex) { tsslUzenet.Text = ex.Message; }
}

private void btnJegy_Click(object sender, EventArgs e)
{
Tanulo t = lstTanulok.SelectedItem as Tanulo;

if (t == null) { tsslUzenet.Text = "Előbb jelölj ki egy tanulót."; return; }

try { t.JegyetBeir((int)nudJegy.Value); Frissit(t); }
catch (ArgumentException ex) { tsslUzenet.Text = ex.Message; }
}

private void btnUtolso_Click(object sender, EventArgs e)
{
Tanulo t = lstTanulok.SelectedItem as Tanulo;

if (t == null) { tsslUzenet.Text = "Előbb jelölj ki egy tanulót."; return; }

tsslUzenet.Text = t.TorolUtolsoJegy() ? "Az utolsó jegy törölve." : "Nincs törölhető jegy.";
Frissit(t);
}

private void btnTorol_Click(object sender, EventArgs e)
{
Tanulo t = lstTanulok.SelectedItem as Tanulo;

if (t != null) { naplo.Torol(t); Frissit(null); }
}

private void Frissit(Tanulo kijelolt)
{
lstTanulok.BeginUpdate();
lstTanulok.Items.Clear();

foreach (Tanulo t in naplo.Tanulok) { lstTanulok.Items.Add(t); } // a ToString jeleníti meg

if (kijelolt != null) { lstTanulok.SelectedItem = kijelolt; }

lstTanulok.EndUpdate();

lblOsztalyAtlag.Text = "Osztályátlag: " + naplo.OsztalyAtlag.ToString("0.00");

Tanulo legjobb = naplo.Legjobb();

lblLegjobb.Text = legjobb == null ? "Legjobb: -" : "Legjobb: " + legjobb.Nev;
}
📷 Képernyőkép helye

Mit tegyél ide: a kész tanulói napló.Javasolt fájlnév: winforms-12-3-kesz.webp · Alt szöveg: A kész tanulói napló

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. Téglalap

Írj Teglalap osztályt Szelesseg, Magassag (pozitív), számított Terulet, Kerulet property-vel.

Egy lehetséges megoldás

A setter kivételt dob 0 vagy negatív értékre; a Terulet => Szelesseg * Magassag számított, nincs mezője, sosem lesz elavult.

2. Bankszámla

Az Egyenleg csak olvasható; Befizet, Kivesz metódus. Mi a szabály?

Egy lehetséges megoldás

private set az egyenlegen; a Kivesz kivételt dob, ha nincs fedezet; a pozitív összeg ellenőrzése; így az egyenleg csak a metóduson át módosul.

3. ToString

Írd felül egy Pont osztály ToString() metódusát (3; 5) alakra.

Egy lehetséges megoldás

public override string ToString() { return "(" + X + "; " + Y + ")"; }. A MessageBox és a ListBox ezt használja.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Mező vagy property?

Melyik kerül private, melyik public szintre?

Megoldás megnyitása

A mező általában private, a property public.

2. Mit jelent a private set?

Kívülről írható-e?

Megoldás megnyitása

Nem: kívülről csak olvasható, az osztályon belül módosítható.

Közepes szint

3. Számított property

Írd meg a Teljes nevet (Vezeteknev + " " + Keresztnev) számított property-ként.

Megoldás megnyitása

public string Teljes => Vezeteknev + " " + Keresztnev; nincs tároló, sosem avul el.

4. Property vagy metódus?

Melyik: (a) Atlag; (b) fájlba mentés; (c) Letrehozva?

Megoldás megnyitása

(a) property; (b) metódus (mellékhatás, lassú); (c) property.

Kihívás szint

5. Védőmásolat

Egy Lista property IReadOnlyList, de a hívó (List<int>)t.Jegyek típuskényszerítéssel mégis módosítja. Hogyan védekezel?

Megoldás megnyitása

Másolatot adsz ki (jegyek.ToList().AsReadOnly()), vagy ReadOnlyCollection<int> burkolót: a kényszerítés nem a belső listát éri el.

6. init-only

Mire jó az { get; init; } (C# 9)?

Megoldás megnyitása

A property csak az objektum létrehozásakor (konstruktor, objektum-inicializáló) állítható; utána változatlan: változatlan (immutable) objektumokhoz.

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Miért ne legyen nyilvános a mező?

Nem ellenőrizhető, és később nem védhető meg a hívók átírása nélkül.

2. Mi az automatikus property?

A { get; set; }: a fordító a rejtett mezőt létrehozza.

3. Mit ellenőriz a setter?

Az érvénytelen értéket kivétellel utasítja el: az objektum érvényes marad.

4. Miért számított az Atlag?

Nem avul el, nincs mit frissíteni.

5. Hogyan adsz ki gyűjteményt?

IReadOnlyList<T> nézetként, módosítás metóduson át.

6. Mikor property, mikor metódus?

Adatszerű, olcsó: property; művelet, mellékhatás: metódus.

7. Mire jó a ToString felülírása?

Az objektum szöveges megjelenéséhez (ListBox, MessageBox).

Mit tudsz már?

  • Mezőket, tulajdonságokat és metódusokat tervezel, és tudod, mi hova való.
  • Az objektum állapotát a setterben és a metódusokban védeded.
  • A belső gyűjteményt csak olvashatóként adod ki.
  • A logikát az osztályba teszed, a Form csak megjelenít.

Következő lecke: Konstruktorok és adatátadás

Érvényes objektum már a létrehozáskor; adatátadás a Formok között konstruktorral, tulajdonsággal és eseménnyel.