Grafikus C# – Windows Forms tanfolyam › 12.5 lecke

Windows Forms · 12.5 lecke · OOP

Hozzáférési szintek

Egy osztály nem azért van, hogy mindenki beleírjon az adataiba. A hozzáférési szintek (public, private, protected, internal) azt szabályozzák, ki láthatja és módosíthatja az osztály tagjait. Az egységbezárás (encapsulation) elve szerint az osztály belső állapota rejtett, kívülről csak néhány, gondosan megtervezett nyilvános művelet érhető el. Így az objektum mindig érvényes állapotban marad, és az osztály belseje szabadon módosítható, anélkül, hogy a többi kód eltörne. Ebben a leckében megtanulod a szinteket, az „annyit mutass, amennyi szükséges” szabályt, és azt, hogyan alkalmazd ezt a Formokra.

  • kb. 90 perc
  • közepes szint
  • egységbezárás
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • ismered a hozzáférési szinteket (public, private, protected, internal, alapértelmezések);
  • megvéded az objektum belső állapotát, és megőrzöd az invariánst (a mindig igaz feltételt);
  • a minimális nyilvános felület elvét alkalmazod;
  • tudod, miért private a Designer vezérlőinek mezője, és hogyan teszel közzé adatot tulajdonsággal;
  • használod a readonly, const és private set módosítókat;
  • értelmezed a CS0122 hibát (elérhetetlen védelmi szint miatt).

Mielőtt elkezded

Ehhez a leckéhez a 12.1–12.4 lecke szükséges (osztály, tulajdonság, konstruktor).

12.4 Konstruktorok →

A hozzáférési szintek

MódosítóKi éri el?Mikor?
publicBárki, bárhonnan.Az osztály szándékos, nyilvános felülete.
privateCsak az osztály saját kódja.Mezők, segédmetódusok: az alapértelmezés a tagoknál.
protectedAz osztály és a leszármazottai.Öröklésnél a leszármazottnak szánt tagok.
internalUgyanabban a projektben (assembly).Projekten belüli segédosztályok; az alapértelmezés az osztályoknál.
protected internalUgyanaz a projekt vagy a leszármazott.Ritka.
private protectedUgyanaz a projekt és a leszármazott.Ritka (C# 7.2).
Peldak.csC#
public class Peldak
{
public int Nyilvanos; // bárhonnan
private int belso; // csak a Peldak osztályból
int alapertelmezett; // kiírás nélkül: private
protected int leszarmazottnak; // a Peldak és a leszármazottai
internal int projektenBelul; // ugyanabban a projektben
}

class Segedosztaly { } // kiírás nélkül: internal (a projekten kívülről nem látszik)

Peldak p = new Peldak();
p.Nyilvanos = 1; // rendben
p.belso = 2; // CS0122: "a Peldak.belso elérhetetlen a védelmi szintje miatt"
AlapértelmezésekA tagok (mezők, metódusok) alapból private, az osztályok alapból internal. A szintet mégis érdemes kiírni: az olvasó azonnal látja a szándékot.
Szűkről bővítsKezdd mindig a legszűkebbel (private), és csak akkor bővíts, ha tényleg kell. A nyilvános tagot később nehéz visszavonni (más kód már használja), a privátot bármikor megnyithatod.
Projekt = assemblyAz internal a lefordított egységre (egy projekt, egy .exe vagy .dll) vonatkozik. Egy WinForms alkalmazásban, egyetlen projektnél, az internal gyakorlatilag „az egész program”.
Fordítási hiba, nem futásiA hozzáférési szint megsértése fordítási hiba (CS0122): a program el sem indul. Ez a védelem: a hibát a fordító találja meg, nem a felhasználó.

Jósolj!

Egy osztálynak int szamlalo; mezője van, módosító nélkül. Egy másik osztály kódja x.szamlalo = 5;. Mi történik?

Jóslat ellenőrzése

Fordítási hiba (CS0122): módosító nélkül a tag private, a másik osztály nem éri el.

Egységbezárás és az invariáns

Bankszamla.csC#
// ROSSZ: a mező nyilvános, bárki elronthatja
public class RosszSzamla { public decimal Egyenleg; }

RosszSzamla r = new RosszSzamla();
r.Egyenleg = -1000000; // semmi nem védi: az invariáns ("nem negatív") megsérült

// JÓ: a belső állapot privát, a művelet őrzi az invariánst
public class Bankszamla
{
private decimal egyenleg; // rejtett
private readonly List<string> naplo = new List<string>();

public decimal Egyenleg { get { return egyenleg; } } // kívülről csak olvasható
public IReadOnlyList<string> Naplo { get { return naplo; } } // a lista módosítható másolata NEM megy ki

public void Befizet(decimal osszeg)
{
if (osszeg <= 0) { throw new ArgumentOutOfRangeException("osszeg", "Pozitív összeg kell."); }

egyenleg += osszeg;
Naploz("Befizetés: +" + osszeg);
}

public bool Kivesz(decimal osszeg)
{
if (osszeg <= 0 || osszeg > egyenleg) { return false; } // az egyenleg sosem lesz negatív

egyenleg -= osszeg;
Naploz("Kivétel: -" + osszeg);

return true;
}

private void Naploz(string s) { naplo.Add(DateTime.Now.ToString("HH:mm:ss") + " " + s); } // belső segéd: nem nyilvános
}
InvariánsAz invariáns az a feltétel, amelynek az objektum teljes életében igaznak kell lennie (itt: az egyenleg nem negatív). Az egységbezárás biztosítja: csak az osztály saját műveletei módosíthatják az állapotot, és azok betartják a szabályt.
Műveletek, nem adatokA kívülről látható felület műveletek (Befizet, Kivesz), nem nyers adatmezők. A hívó nem tudja és nem is kell tudnia, hogyan tárolódik az egyenleg.
Szabad átalakításMivel az egyenleg privát, később decimal helyett fillérekben (long) tárolhatod: a külvilág kódja nem törik el. Ez az egységbezárás szabadsága.
A kimenő lista védelmeHa a naplo listát közvetlenül adnád ki, a hívó törölhetné az elemeit. Az IReadOnlyList<T> csak olvasható nézetet ad (a belső lista védve marad).

Jósolj!

A Bankszamla osztályban a public List<string> Naplo { get; } tulajdonság a belső listát adja ki. A hívó szamla.Naplo.Clear(); kódot ír. Mi történik?

Jóslat ellenőrzése

A napló kiürül: a get-only tulajdonság csak a hivatkozást védi (nem lehet másik listát hozzárendelni), de a lista tartalmát nem. Ezért kell az IReadOnlyList<T>, vagy másolat (new List<string>(naplo)).

Hozzáférési szintek a Formokban

SzerkesztoForm.csC#
// a Designer a vezérlők mezőit PRIVÁTRA generálja: kívülről nem érhetők el
private System.Windows.Forms.TextBox txtNev;

// ha kívülről kell az érték: tulajdonságot adunk, nem a vezérlőt tesszük nyilvánossá
public string Nev
{
get { return txtNev.Text.Trim(); }
set { txtNev.Text = value; }
}

// ROSSZ megoldás: a vezérlő nyilvánossá tétele (Properties → Modifiers = Public)
// a hívó bármit megtehet vele: Visible=false, Text=..., Dispose()... és a Form belseje nem cserélhető

// az eseménykezelők privátok: nincs rá szükség, hogy más hívja
private void btnMent_Click(object sender, EventArgs e) { /* ... */ }

// a nyilvános felület: csak ami a hívónak kell
public event EventHandler Mentve;
public Szemely Eredmeny { get; private set; }
Vezérlők: privátA vezérlők a Form belső ügyei. A Designer ezért private mezőként generálja őket. Ha a Properties → Modifiers beállítást Public-ra állítod, a hívó közvetlenül hozzáférne: szoros csatolás (12.4 lecke).
Tulajdonság a vezérlő helyettAz érték olvasásához és írásához public tulajdonságot adj (Nev), amely a vezérlőt elrejti. Ha később a TextBox-ot ComboBox-ra cseréled, csak a tulajdonság belseje változik.
Eseménykezelők privátokA btnMent_Click senkinek sem hívható: nincs oka nyilvánosnak lenni. A Designer is privátot generál.
Mi legyen nyilvános a Formon?Csak az, ami a hívónak kell: a kezdőadat (konstruktor), az eredmény (tulajdonság) és az értesítés (esemény). Minden más privát.

Jósolj!

A főablak kódja szerkeszto.txtNev.Text = "x"; sort tartalmaz, és a txtNev a Designer szerint private. Mi történik?

Jóslat ellenőrzése

Fordítási hiba (CS0122). A helyes megoldás nem a Modifiers = Public, hanem egy Nev tulajdonság a SzerkesztoForm-ban.

readonly, const, private set

Modositok.csC#
public class Beallitasok
{
public const int MaxElem = 100; // fordítási idejű állandó, soha nem változik
private readonly string fajlUt; // csak a konstruktorban kaphat értéket

public Beallitasok(string ut) { fajlUt = ut; } // a readonly mező itt még írható
public string FajlUt { get { return fajlUt; } }

public int Elemek { get; private set; } // kívülről olvasható, csak belülről írható

public void Hozzaad()
{
if (Elemek < MaxElem) { Elemek++; }
}
}
constFordításkor eldőlő, soha nem változó érték (const double Pi2 = 6.28). Implicit static; csak egyszerű típus lehet.
readonlyFutás közben, a konstruktorban kap értéket, utána nem módosítható. Olyan adathoz való, amely objektumonként más, de az élete során nem változik.
private setA tulajdonság kívülről csak olvasható, belülről írható. A leggyakoribb módszer az invariáns védelmére.
Melyiket?Állandó szám: const; objektumonként más, de fix: readonly mező; kívülről olvasható, belülről változó: private set.

Jósolj!

A fajlUt readonly mezőt egy metódus (nem a konstruktor) próbálja módosítani: fajlUt = "uj.txt";. Mi történik?

Jóslat ellenőrzése

Fordítási hiba (CS0191): a readonly mezőt csak a deklarációnál vagy a konstruktorban lehet írni.

📷 Képernyőkép helye

Mit tegyél ide: a bankszámla-labor: sikeres és elutasított művelet, a napló.Javasolt fájlnév: winforms-12-5-bankszamla.webp · Alt szöveg: A bankszámla-labor 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. CS0122: elérhetetlen a védelmi szint miatt

szamla.egyenleg = 100;   // private mező
Hiba és javítás

Nem a mezőt kell nyilvánossá tenni: használd a Befizet műveletet. A mező az osztály belső ügye.

2. A negatív egyenleg

public decimal Egyenleg { get; set; }
Hiba és javítás

A nyilvános set megkerüli az ellenőrzést. private set, és a módosítás a Befizet / Kivesz metóduson át.

3. A napló kívülről törölhető

public List<string> Naplo { get; } = new List<string>();
Hiba és javítás

A get-only a hivatkozást védi, nem a tartalmat. IReadOnlyList<string> a kimenő típus.

4. A vezérlő nyilvános lett

A hívó közvetlenül írja a txtNev.Text értéket, és a Form átalakítása minden hívást eltör.

Ok és javítás

A Modifiers = Public beállítás szoros csatolást okoz. Állítsd vissza Private-ra, és adj tulajdonságot.

5. CS0191: a readonly mező nem írható

private readonly int max = 10;
void Allit() { max = 20; }
Hiba és javítás

A readonly mező csak a konstruktorban (vagy a deklarációnál) írható. Ha változnia kell, ne legyen readonly.

6. Minden public

Az osztály minden tagja public, mert „úgy működik”.

Ok és javítás

Később nehéz átalakítani, mert bárki használja. Fordítsd meg: minden legyen private, és csak a szükségeset nyisd meg.

Mini projekt: bankszámla-labor

Készíts programot, amely egy védett belső állapotú számlát kezel, és az egységbezárás előnyeit mutatja:

  1. Bankszamla osztály: privát egyenleg, private readonly tulajdonos és azonosító; Egyenleg (csak olvasható), Tulajdonos; a konstruktor kezdő összeggel (nem negatív); Befizet (kivétel hibás összegre), Kivesz (bool), Atutal(Bankszamla, osszeg).
  2. Napló: privát lista, IReadOnlyList<string> Naplo; a belső Naploz privát metódus.
  3. Limit: public const decimal NapiLimit = 200000; a Kivesz a napi összeg ellenőrzését is végzi (privát mező a mai kivétekről).
  4. Felület: két számla (Anna, Béla); nudOsszeg, Befizet, Kivesz, Átutal Annáról Bélára; az egyenlegek és a napló kijelzése; a hibák az állapotsorban.
  5. Védelem-kísérletek: a leírásban jegyezd fel, mit írna a fordító, ha a szamla.egyenleg = 1000; sort kikommentezett kódként beírnád (CS0122), és ha a Naplo List<string> lenne (a Clear() hatása).
  6. ToString(): Anna: 1 250 Ft (a ListBoxban használható).
  7. Hibatűrés: negatív és nulla összeg, túl nagy kivétel, átutalás önmagára, null célszámla, a napi limit túllépése.
  8. Teszteld: minden hibás eset, az egyenleg összege átutalás után (az összes pénz megmarad), a napló sorrendje.
Megoldás megnyitása (próbáld ki előbb önállóan!)
Bankszamla.cs és Form1.cs (using System.Collections.Generic;)C#
public class Bankszamla
{
public const decimal NapiLimit = 200000m;

private decimal egyenleg;
private decimal maiKivet = 0;
private DateTime maiNap = DateTime.Today;
private readonly List<string> naplo = new List<string>();
private readonly string tulajdonos;

public decimal Egyenleg { get { return egyenleg; } }
public string Tulajdonos { get { return tulajdonos; } }
public IReadOnlyList<string> Naplo { get { return naplo; } }

public Bankszamla(string tulajdonos, decimal kezdo)
{
if (string.IsNullOrWhiteSpace(tulajdonos)) { throw new ArgumentException("A tulajdonos kötelező.", "tulajdonos"); }
if (kezdo < 0) { throw new ArgumentOutOfRangeException("kezdo", "A kezdő összeg nem lehet negatív."); }

this.tulajdonos = tulajdonos.Trim();
egyenleg = kezdo;
Naploz("Megnyitás: " + kezdo.ToString("N0"));
}

public void Befizet(decimal osszeg)
{
if (osszeg <= 0) { throw new ArgumentOutOfRangeException("osszeg", "Pozitív összeg kell."); }

egyenleg += osszeg;
Naploz("Befizetés: +" + osszeg.ToString("N0"));
}

public bool Kivesz(decimal osszeg)
{
NapFrissit();

if (osszeg <= 0 || osszeg > egyenleg || maiKivet + osszeg > NapiLimit)
{
Naploz("Elutasítva: -" + osszeg.ToString("N0"));
return false;
}

egyenleg -= osszeg;
maiKivet += osszeg;
Naploz("Kivétel: -" + osszeg.ToString("N0"));

return true;
}

public bool Atutal(Bankszamla cel, decimal osszeg)
{
if (cel == null) { throw new ArgumentNullException("cel"); }
if (ReferenceEquals(cel, this)) { throw new ArgumentException("Önmagára nem utalhatsz.", "cel"); }

if (!Kivesz(osszeg)) { return false; }

cel.Befizet(osszeg);
Naploz("Átutalás → " + cel.Tulajdonos);

return true;
}

private void NapFrissit()
{
if (maiNap != DateTime.Today) { maiNap = DateTime.Today; maiKivet = 0; }
}

private void Naploz(string s) { naplo.Add(DateTime.Now.ToString("HH:mm:ss") + " " + s); }

public override string ToString() { return tulajdonos + ": " + egyenleg.ToString("N0") + " Ft"; }
}

// ---- Form1.cs ----
private readonly Bankszamla anna = new Bankszamla("Anna", 50000);
private readonly Bankszamla bela = new Bankszamla("Béla", 20000);

private void Frissit()
{
lblAnna.Text = anna.ToString();
lblBela.Text = bela.ToString();
lstNaplo.Items.Clear();

foreach (string s in anna.Naplo) { lstNaplo.Items.Add("Anna " + s); }
foreach (string s in bela.Naplo) { lstNaplo.Items.Add("Béla " + s); }
}

private void Vegrehajt(Func<bool> muvelet, string hiba)
{
try { tsslUzenet.Text = muvelet() ? "Kész." : hiba; }
catch (ArgumentException ex) { tsslUzenet.Text = ex.Message; } // ArgumentOutOfRangeException is ArgumentException

Frissit();
}

private void btnBefizet_Click(object sender, EventArgs e) { Vegrehajt(() => { anna.Befizet(nudOsszeg.Value); return true; }, ""); }
private void btnKivesz_Click(object sender, EventArgs e) { Vegrehajt(() => anna.Kivesz(nudOsszeg.Value), "Elutasítva (egyenleg vagy napi limit)."); }
private void btnAtutal_Click(object sender, EventArgs e) { Vegrehajt(() => anna.Atutal(bela, nudOsszeg.Value), "Az átutalás elutasítva."); }
📷 Képernyőkép helye

Mit tegyél ide: a kész bankszámla-labor.Javasolt fájlnév: winforms-12-5-kesz.webp · Alt szöveg: A kész bankszámla-labor

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. Hőmérő

Írj Homero osztályt, amelynek a hőmérséklete nem mehet −273,15 °C alá. Hogyan védi az invariánst?

Egy lehetséges megoldás

Privát mező, nyilvános tulajdonság set ellenőrzéssel (kivétel a határ alatt), vagy private set és Allit(double) metódus.

2. Védett lista

Egy osztály listát tart, amit kívülről nem lehet módosítani. Három lehetőség?

Egy lehetséges megoldás

IReadOnlyList<T> kimenet; másolat visszaadása (new List<T>(lista)); ReadOnlyCollection<T> (lista.AsReadOnly()).

3. Formok áttekintése

Nézd át egy korábbi projektedet: melyik vezérlő lett nyilvános, és minek?

Egy lehetséges megoldás

Keresd a Modifiers = Public beállítást a Designerben (public System.Windows.Forms... a .Designer.cs végén). Cseréld privátra, és adj tulajdonságot a szükséges értékhez.

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. Alapértelmezések

Mi az alapértelmezett szint egy mezőnél és egy osztálynál?

Megoldás megnyitása

A mező: private; az osztály: internal.

2. Melyik kell?

Melyik módosítót használod egy kívülről olvasható, belülről írható számlálóra?

Megoldás megnyitása

public int Szamlalo { get; private set; }

Közepes szint

3. const vagy readonly?

Egy objektum létrehozási időpontját tárolod: melyik?

Megoldás megnyitása

readonly: objektumonként más (futáskor dől el), de nem változik. A const fordításkor ismert értékhez való.

4. Invariáns

Mi az invariáns, és hogyan védi az egységbezárás?

Megoldás megnyitása

Az objektum egész életében igaz feltétel; csak az osztály saját, ellenőrző műveletei módosíthatják az állapotot.

Kihívás szint

5. Tulajdonság és list

Miért nem véd a public List<T> L { get; }?

Megoldás megnyitása

A get-only a hivatkozás cseréjét tiltja, a lista tartalmát bárki módosíthatja (Add, Clear).

6. internal

Mikor számít a különbség a public és az internal között?

Megoldás megnyitása

Ha több projekt (osztálykönyvtár) van: az internal tag csak a saját projektben látszik. Egyetlen projektnél gyakorlatilag nincs különbség.

Gyors önellenőrzés

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

1. Mi az egységbezárás?

A belső állapot elrejtése; kívülről csak a gondosan megtervezett műveletek érhetők el.

2. Mi a private?

Csak az osztály saját kódja éri el.

3. Mit generál a Designer a vezérlőkre?

private mezőket.

4. Hogyan teszel közzé adatot a Formból?

Nyilvános tulajdonsággal, nem a vezérlő nyilvánossá tételével.

5. Mit jelent a CS0122?

Elérhetetlen tag a védelmi szint miatt (fordítási hiba).

6. Mire való a private set?

Kívülről olvasható, csak belülről írható tulajdonság.

7. Melyik elv szerint kezdesz?

A legszűkebb szinttel (private), és csak szükség esetén bővítesz.

Mit tudsz már?

  • Ismered a hozzáférési szinteket és az alapértelmezéseket.
  • Megvéded az objektum állapotát és az invariánst.
  • A Formokban privát vezérlőket és nyilvános tulajdonságokat használsz.
  • Helyesen használod a readonly, const és private set módosítókat.

Következő lecke: Adatmodellek és felelősségek

Az adatot hordozó osztályok (modellek) tervezése, az egy felelősség elve, a modell és a felület szétválasztása.