Grafikus C# – Windows Forms tanfolyam › 13.3 lecke Windows Forms · 13.3 lecke · hibakeresés A töréspont megállítja a programot, de a hibakeresés igazi ereje akkor jön elő, amikor belenézel a megállított program belsejébe: mi a változók értéke, mi a kifejezés eredménye, és hogyan jutott ide a program. Ehhez három eszközt használunk: a Locals/Autos/Watch ablakokat (a változók és kifejezések értékei), az Immediate ablakot (kifejezések kiértékelése és kipróbálása menet közben), és a Call Stacket (a hívási lánc). Megtanulod azt is, hogyan viselkedik a hibakereső kivételnél, és hogyan dolgozz tudományos módszerrel: hipotézis, megfigyelés, javítás, ellenőrzés.Locals, Watch és Call Stack

A lecke végére
- használod a Locals, Autos és Watch ablakot a változók és kifejezések értékeinek figyelésére;
- használod a DataTipet (egérmutató) és a Quick Watchot (Shift+F9);
- menet közben módosítod a változók értékét hipotézisek kipróbálására;
- az Immediate ablakban kifejezéseket értékelsz ki, és metódust hívsz;
- olvasod a Call Stacket, és a keretek között váltva látod, ki hívta kit;
- kivételnél megállítod a hibakeresőt, és a
$exceptionváltozóból olvasod az okot; - hibát keresel módszeresen (reprodukálás, hipotézis, felező keresés).
Mielőtt elkezded
Ehhez a leckéhez a 13.2 lecke (töréspont, léptetés) szükséges.
Locals, Autos, Watch, DataTip
| Eszköz | Hol? | Mit mutat? |
|---|---|---|
| Locals | Debug → Windows → Locals | Az aktuális metódus összes helyi változója és paramétere, értékkel. |
| Autos | Debug → Windows → Autos | A megállási sorhoz közeli változók és az előző sor eredménye. |
| Watch 1–4 | Debug → Windows → Watch | Az általad megadott kifejezések (osszeg / lista.Count, nev.Length), amelyek léptetés közben frissülnek. |
| DataTip | egér a változó fölé | Gyors értékmutatás; kinyitható; kitűzhető (pin). |
| Quick Watch | Shift+F9 | Egy kifejezés egyszeri kiértékelése párbeszédablakban. |
private double Atlag(List<int> jegyek)
{
int osszeg = 0;
foreach (int j in jegyek)
{
osszeg += j; // ● töréspont: a Locals ablakban: j, osszeg, jegyek (kinyitható: Count, elemek)
}
return (double)osszeg / jegyek.Count; // Watch: osszeg / jegyek.Count (hibás: egész osztás!) és (double)osszeg / jegyek.Count
}List, tömb vagy objektum mellett nyíl van: kinyitva látod a tagjait és elemeit. Egy List-nél a Count és az elemek; a string mellett a nagyító ikon (Text Visualizer) hosszú szöveg olvasásához.osszeg-ot). Így kipróbálod a hipotézist: „ha ez a szám 5 lenne, jó lenne-e a vége?” Nem kell újraindítani és újragépelni. Ez ideiglenes: a kódot a javítás során írd át.jegyek.Count > 0, nev.ToUpper(), osszeg / 2.0). A kifejezések értékét minden lépésnél újraszámolja. Mellékhatásos kifejezést (módosító metódust) ne írj ide.Jósolj!
A Locals ablakban az osszeg és a j mellett a jegyek is látszik. A jegyek Count értéke 0, de a ciklus (foreach) még nem futott le. Mi lesz a return soron az (double)osszeg / jegyek.Count kifejezés értéke?
Jóslat ellenőrzése
A valós osztás nullával nem kivétel, hanem NaN (vagy végtelen): 0.0 / 0 = NaN, 5.0 / 0 = ∞. Egész osztásnál viszont DivideByZeroException. A Watch ablak azonnal megmutatja a különbséget a két kifejezéssel, mielőtt a sor lefutna.
Az Immediate ablak
> ? osszeg
15
> ? jegyek.Count
3
> ? (double)osszeg / jegyek.Count
5
> ? jegyek[0] + jegyek[1]
9
> osszeg = 100 (érték módosítása)
100
> ? txtNev.Text.Trim().ToLower()
"anna"
> ? Atlag(new List<int> { 5, 4, 3 }) (metódus hívása)
4? kifejezés (vagy ? nélkül is). Kipróbálhatsz egy javítást mielőtt átírod a kódot: működne-e nev.Trim()? Mit ad a Math.Round(2.5)?? Atlag(new List<int> { 5, 4, 3 }). Így a logikát kipróbálod a gombnyomás és a felület nélkül.osszeg = 100 a változó értékét átírja a futó programban (mint a Locals-ban). Mellékhatással jár: a program további futását befolyásolja.lista.Clear() törli a listát, egy Mentes() mentést végez. Csak ártalmatlan kifejezéseket írj.Jósolj!
A megállított programban az Immediate ablakba beírod: ? lista.Remove(lista[0]). Mi történik a programmal?
Jóslat ellenőrzése
A kifejezés valóban eltávolítja az első elemet a futó program listájából (és True-t ad vissza): a további futás már a módosított listával történik. Az Immediate ablak nem „csak olvas”: a mellékhatás valós.
A Call Stack: hogyan jutott ide a program?
Name Language
► Kosar.exe!Kosar.Form1.Osszeg() Line 52 C# ← itt állsz (legfelül: az aktuális metódus)
Kosar.exe!Kosar.Form1.Kiir() Line 41 C# ← ezt hívta az Osszeg
Kosar.exe!Kosar.Form1.btnOsszesit_Click() Line 28 C# ← a gombkezelő hívta a Kiir-t
[External Code] ← a Windows Forms keretrendszer (az eseményt kiváltotta)btnOsszesit_Click) a belépési pont, azt a keretrendszer hívja.ex.StackTrace): ugyanazt mutatja, hogy hol és milyen úton keletkezett. A felülről lefelé olvasott sorokban az első a keletkezés helye, az utolsó a belépési pont.Jósolj!
Az Osszeg() metódusban kivétel keletkezik egy rossz paraméter miatt. A hiba oka valójában a Kiir() hívó metódusban van (hibás argumentumot ad át). Melyik keretet nézed meg a Call Stackben?
Jóslat ellenőrzése
A második keretet (Kiir()): rákattintva a Locals mutatja, milyen értéket adott át. A hiba helye (amelyik metódusban a kivétel keletkezett) gyakran nem az ok helye: az ok néha a hívóban van.
Kivétel a hibakeresőben
private void btnSzamol_Click(object sender, EventArgs e)
{
string szoveg = txtSzam.Text;
int n = int.Parse(szoveg); // ha a szöveg "abc": FormatException; a hibakereső MEGÁLL ezen a soron
lblEredmeny.Text = (100 / n).ToString(); // ha n == 0: DivideByZeroException
}$exception a most keletkezett kivételre hivatkozik: $exception.Message, $exception.StackTrace, $exception.InnerException. Hasznos catch blokkban is (a kivétel változójával: ex).try–catch elkapja. Így láthatod azokat a kivételeket is, amelyeket egy catch elnyel (és „elveszett” hibát okoz).ex értékeit. Az üres catch { } a legrosszabb hibaelhárítási akadály: elnyeli a kivételt, és semmit sem mutat (13.4 lecke).Jósolj!
Egy try { Mentes(); } catch { } blokk elnyeli a mentési kivételt, és a felhasználó azt hiszi, hogy a mentés sikerült. Hogyan veszed észre hibakereséskor?
Jóslat ellenőrzése
Az Exception Settings ablakban a Common Language Runtime Exceptions mellett Break When Thrown: a hibakereső a dobás pillanatában megáll, és megmutatja a kivételt, bár a catch elnyelné. (Hosszú távon: a catch blokk naplózzon, vagy ne nyelje el.)
Hibakeresés módszeresen
A tudományos módszer
- Reprodukáld a hibát: pontos lépések, amelyek mindig előidézik.
- Szűkítsd a helyét: hol (melyik metódus) romlik el? Felező kereséssel.
- Állíts fel hipotézist: „az
osszegazért rossz, mert a ciklus eggyel korábban áll meg.” - Teszteld a hipotézist (Watch, Immediate, értékmódosítás): megerősíti vagy cáfolja?
- Javíts egyet, és ellenőrizd. Ne módosíts egyszerre több dolgot.
- Ellenőrizd a határeseteket is, hogy új hiba nem keletkezett-e.
Szokások, amelyek segítenek
- Olvasd el a hibaüzenetet és a stack trace-t, ne találgass.
- Gumikacsa-módszer: magyarázd el a kódot hangosan (egy kacsának, egy barátnak): az elmondás közben gyakran kiderül a hiba.
- Kicsinyíts: készíts minimális példát a hibára; így az ok kevés kódra szűkül.
- Verziókezelés: mi változott, amióta még működött? (A
git diffa legjobb barát.) - Szünet: a fáradt agy nem lát. Egy séta után gyakran „magától” megjön az ötlet.
- Írd le a hibát és a megoldást: legközelebb gyorsabb.
Jósolj!
Három dolgot módosítasz egyszerre (a ciklus határát, egy feltételt és egy kezdőértéket), és a hiba eltűnik. Két nappal később újra előjön, más bemenetnél. Mi volt a hiba a módszerben?
Jóslat ellenőrzése
Hogy egyszerre többet változtattál: nem tudod, melyik módosítás oldotta meg a tényleges hibát, melyik csak elfedte a tünetet. Egy lépés, egy ellenőrzés, és mindig a hiba okát keresd, ne a tünetet.
Mit tegyél ide: a Visual Studio hibakereső nézete: Locals, Watch, Immediate és Call Stack ablakok egy megállított programban.Javasolt fájlnév: winforms-13-3-ablakok.webp · Alt szöveg: A hibakereső ablakai
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. A Watch ablak „nem létezik” üzenetet ad
A A változó nem abban a metódusban él, ahol a program áll: a hatókör (scope) eltér. A Call Stackben válts a megfelelő keretre, vagy a program a változó deklarálása előtt áll (még nem létezik).osszeg mellett The name 'osszeg' does not exist in the current context látszik.Ok és javítás
2. A Locals értékei furcsák
Értelmetlen számok látszanak a változókon. A változó értékét még nem állítottad be (a sor még nem futott le), vagy Release konfigurációban optimalizált. Lépj tovább egyet, és állítsd Debug-ra.Ok és javítás
3. Az Immediate ablak megváltoztatta a programot
Kipróbáltál egy kifejezést, és a program másképp viselkedik. A kifejezésnek mellékhatása volt (módosított adatot). Indítsd újra a programot, és csak olvasó kifejezéseket használj.Ok és javítás
4. Nincs hívó a Call Stackben
Csak egy [External Code] sor áll a verem alján, és nem látod a hívót. A hívó nem a te kódod (az eseményt a Windows Forms váltotta ki), ezért ez a belépési pont. A Show External Code opcióval megnézheted a keretrendszer részeit, de ritkán szükséges.Ok és javítás
5. A kivétel nem a hibás sorban állt meg
A program egy távoli sorban dob kivételt, a hiba oka másutt van. A Call Stackben menj felfelé a hívási láncon: a hibás érték valószínűleg egy korábbi metódusban keletkezett (például Ok és javítás
null, üres lista).
6. A catch elnyeli a hibát
try { Mentes(); } catch { } // semmit sem mutatHiba és javítás
Állítsd be a Break When Thrown beállítást a kivétel megtalálásához, és a catch blokk naplózzon vagy jelezzen a felhasználónak (13.4 és 13.7 lecke).
Mini projekt: rejtélyes hibák a kosárban
Kapsz egy egyszerű webshop-kosár programot három rejtélyes hibával. A hibákat nem kitalálással, hanem a hibakereső eszközeivel találd meg: töréspont, Locals/Watch, Immediate, Call Stack:
- Projekt: Windows Forms (
Kosar);txtNev,nudDb,btnHozzaad,btnDuplaz(az első tétel duplázása),btnOsszesit,lstKosar,lblOsszeg. - Kód: másold be a hibás programot (lent); a
Termekosztály külön fájlban vagy a Form alján. - 1. rejtély: az első Hozzáad gombnyomásra a program összeomlik. Használd az Exception Helpert és a
$exceptionértéket; döntsd el az okot és a javítást. - 2. rejtély: az alma hozzáadása működik, az Alma (nagy kezdőbetű) nem. A Locals és az Immediate (
? arak.ContainsKey(nev),? arak.Keys) segítségével találd meg az okot. - 3. rejtély: felvettél 2 db almát (120 Ft/db), majd a Duplázás után az összeg 240 + 480 = 720 Ft helyett 960 Ft lesz. A Locals Make Object ID funkciójával (jobb klikk az objektumon) vagy a
ReferenceEqualskifejezéssel (? object.ReferenceEquals(kosar[0], kosar[1])) bizonyítsd be, mi történik. - Call Stack gyakorlása: a Összesít gomb a
btnOsszesit_Click→Kiir→Osszegláncot hívja; azOsszegbelsejében tégy töréspontot, és a Call Stackben váltogass a keretek között: írd le, mi akosartartalma aKiirkeretében. - Hipotézisnapló: minden hibához: tünet, hipotézis, mivel ellenőriztem (eszköz), eredmény, javítás.
- Javítás és ellenőrzés: javítsd a három hibát; ellenőrizd a példákkal: 2 alma + 1 körte = 2·120 + 200 = 440 Ft; a 2 almás tétel duplázása után egy új, 4 darabos almás sor kerül a kosárba: 440 + 4·120 = 920 Ft (a hibás programban a két sor ugyanaz az objektum, ezért az összeg 4·120 + 200 + 4·120 = 1160 Ft lenne).
A hibás program (próbáld ki előbb önállóan!)
public class Termek
{
public string Nev;
public int Ar;
public int Db;
}
public partial class Form1 : Form
{
private List<Termek> kosar; // 1. rejtély
private Dictionary<string, int> arak = new Dictionary<string, int> { { "alma", 120 }, { "korte", 200 } };
private void btnHozzaad_Click(object sender, EventArgs e)
{
string nev = txtNev.Text;
kosar.Add(new Termek { Nev = nev, Ar = arak[nev], Db = (int)nudDb.Value }); // 1. és 2. rejtély
Kiir();
}
private void btnDuplaz_Click(object sender, EventArgs e)
{
Termek masolat = kosar[0]; // 3. rejtély
masolat.Db = masolat.Db * 2;
kosar.Add(masolat);
Kiir();
}
private void btnOsszesit_Click(object sender, EventArgs e) { Kiir(); }
private void Kiir()
{
lstKosar.Items.Clear();
foreach (Termek t in kosar) { lstKosar.Items.Add(t.Nev + " x " + t.Db + " = " + t.Ar * t.Db + " Ft"); }
lblOsszeg.Text = "Összesen: " + Osszeg() + " Ft";
}
private int Osszeg()
{
int osszeg = 0;
foreach (Termek t in kosar) { osszeg += t.Ar * t.Db; }
return osszeg;
}
}Tippek (ha elakadsz): az 1. rejtély kivételének típusa NullReferenceException; a Locals-ban a kosar értéke null. A 2. a szótár kulcsainak kis-nagybetű érzékenysége. A 3. egy objektum, két hivatkozás (12.1 lecke).
A javított program
public partial class Form1 : Form
{
private readonly List<Termek> kosar = new List<Termek>(); // 1. a lista létrehozva (a mező null volt)
private readonly Dictionary<string, int> arak = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase) { { "alma", 120 }, { "korte", 200 } }; // 2. kis-nagybetű nélküli kulcsok
private void btnHozzaad_Click(object sender, EventArgs e)
{
string nev = txtNev.Text.Trim();
int ar;
if (!arak.TryGetValue(nev, out ar)) { tsslUzenet.Text = "Ismeretlen termék: " + nev; return; } // 2. a hiányzó kulcs sem dob kivételt
kosar.Add(new Termek { Nev = nev, Ar = ar, Db = (int)nudDb.Value });
Kiir();
}
private void btnDuplaz_Click(object sender, EventArgs e)
{
if (kosar.Count == 0) { return; }
Termek eredeti = kosar[0];
Termek masolat = new Termek { Nev = eredeti.Nev, Ar = eredeti.Ar, Db = eredeti.Db * 2 }; // 3. valódi másolat: új objektum
kosar.Add(masolat);
Kiir();
}
private void btnOsszesit_Click(object sender, EventArgs e) { Kiir(); }
private void Kiir()
{
lstKosar.Items.Clear();
foreach (Termek t in kosar) { lstKosar.Items.Add(t.Nev + " x " + t.Db + " = " + t.Ar * t.Db + " Ft"); }
lblOsszeg.Text = "Összesen: " + Osszeg() + " Ft";
}
private int Osszeg()
{
int osszeg = 0;
foreach (Termek t in kosar) { osszeg += t.Ar * t.Db; }
return osszeg;
}
}Mit tegyél ide: a 3. rejtély a Locals ablakban (azonos Object ID a két listaelemnél).Javasolt fájlnév: winforms-13-3-rejtely.webp · Alt szöveg: Az azonos objektum két hivatkozása a Locals ablakban
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. Watch kifejezések
Egy ciklusban követned kell, hogy az osszeg túllépte-e a 100-at. Mit írsz a Watch ablakba?Egy lehetséges megoldás
osszeg > 100 (logikai kifejezés); a Watch léptetés közben true/false értékre vált. Ugyanezt feltételes töréspontnak is beállíthatod.
2. Hívási lánc rajzolása
Egy saját programodban tégy töréspontot egy mélyen hívott metódusba, és rajzold le a Call Stacket. A rajzon: gomb → kezelő → metódus A → metódus B → metódus C; mindenhol az átadott paraméterek (a Locals-ból olvasva). A rajz tanulságos, hol romlott el az adat.Egy lehetséges megoldás
3. Immediate kísérlet
Az Immediate ablakban próbáld ki, hogyan viselkedik a A Math.Round(2.5), a Math.Round(3.5) és a (int)2.9.Egy lehetséges megoldás
Math.Round(2.5) és a Math.Round(3.5) eredménye 2 és 4 (kerekítés a páros felé), az (int)2.9 2 (levágás). Ez magyarázza a „furcsa” kerekítéseket: MidpointRounding.AwayFromZero kell, ha a tanult szabályt várod.
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. Locals vagy Watch?
Mi a különbség? A Locals az aktuális metódus összes helyi változóját mutatja automatikusan; a Watch az általad megadott kifejezéseket figyeli.Megoldás megnyitása
2. Mi a Call Stack?
Fogalmazd meg egy mondatban. A metódushívások lánca a belépési ponttól az aktuális sorig; a legfelső az aktuális metódus.Megoldás megnyitása
Közepes szint
3. Immediate ablak
Mire használod, és mire kell vigyázni? Kifejezések kiértékelésére és kipróbálására megállított programban; a kifejezések valóban lefutnak (mellékhatás!).Megoldás megnyitása
4. Piros érték
Mit jelent a pirosra festett érték a Locals-ban? Az előző lépés óta megváltozott: segít látni, melyik sor mit módosított.Megoldás megnyitása
Kihívás szint
5. Ok vs. tünet
A kivétel egy metódusban keletkezik, a hiba oka a hívóban van. Hogyan találod meg? A Call Stackben duplán kattintasz a hívó keretre: a Locals a hívó állapotát mutatja, és látod, milyen hibás értéket adott át.Megoldás megnyitása
6. Felező keresés
A hiba egy 8 lépéses folyamat valamelyikében van. Legfeljebb hány töréspontos megállással találod meg? 3 megállással (8 → 4 → 2 → 1): mindig a folyamat közepén ellenőrzöd, hogy az érték még jó-e.Megoldás megnyitása
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Az aktuális metódus helyi változóit és paramétereit, értékkel. Általad megadott kifejezések értékeinek figyelésére léptetés közben. Kifejezések kiértékelésére és metódusok hívására megállított programban. Hogy a program milyen hívási láncon jutott az aktuális sorhoz. A frissen keletkezett kivételre hivatkozó pszeudó-változó a hibakeresőben. Reprodukálás, szűkítés, hipotézis, ellenőrzés, egy javítás, újra-ellenőrzés. Mindig a folyamat közepén ellenőrzöd az értéket, így gyorsan szűkíted a hiba helyét.1. Mit mutat a Locals?
2. Mire jó a Watch?
3. Mire jó az Immediate ablak?
4. Mit mutat a Call Stack?
5. Mi az a $exception?
6. Mi a hibakeresés tudományos módszere?
7. Mi a felező keresés?
Mit tudsz már?
- A Locals, Watch és Immediate ablakkal belenézel a megállított programba.
- Olvasod a Call Stacket, és a keretek között váltva követed az adatot.
- Kivételnél a hibakereső segítségével megtalálod az okot.
- Módszeresen keresel hibát: hipotézis, megfigyelés, egy javítás.
Következő lecke: try, catch és finally
A futási hibák kezelése: a kivételek elkapása, a finally takarítás és a hibák felelős kezelése.



