Grafikus C# – Windows Forms tanfolyam › 13.9 lecke

Windows Forms · 13.9 lecke · erőforrások

Erőforrás-kezelés és teljesítmény

A program lefut, a tesztek zöldek, mégis: egy óra használat után lassú, a memóriahasználat a duplájára nőtt, a rajzolás akadozik, és végül „Out of memory” vagy „Error creating window handle” hibával leáll. Ezek az erőforrás-szivárgás és a teljesítményhibák jellemző tünetei, és gyakran csak hosszabb használat után jelentkeznek. A .NET szemétgyűjtője (garbage collector) a memória nagy részét rendben tartja, de nem mindent: a nem felügyelt erőforrásokat (képek, ecsetek, fájlkezelők, kapcsolatok) neked kell felszabadítanod, és a hivatkozásokat (esemény-feliratkozás, statikus lista) neked kell elengedned. Ebben a leckében megtanulod az IDisposable és a using helyes használatát, a leggyakoribb szivárgásokat, a mérési módszereket (mérj, ne találgass), és a felület gyorsításának alapvető fogásait.

  • kb. 110 perc
  • haladó szint
  • teljesítmény
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • megkülönbözteted a felügyelt (GC) és a nem felügyelt erőforrásokat;
  • helyesen használod az IDisposable-t és a using blokkot (Bitmap, Graphics, Font, Pen, stream, kapcsolat);
  • felismered a tipikus szivárgásokat: felszabadítatlan kép, esemény-feliratkozás, statikus gyűjtemény, nyitva maradt Form;
  • méred a memóriát és a sebességet (Feladatkezelő, Diagnostic Tools, Stopwatch);
  • gyorsítod a felületet (BeginUpdate, SuspendLayout, DoubleBuffered, VirtualMode);
  • jobb algoritmust és adatszerkezetet választasz (StringBuilder, HashSet, Dictionary).

Mielőtt elkezded

Ehhez a leckéhez a 9.8 lecke (képek felszabadítása, Dispose), a 11. fejezet (háttérmunka) és a 13.2–13.3 lecke (hibakereső) szükséges.

13.8 Manuális tesztelés →

Felügyelt és nem felügyelt erőforrások, IDisposable

ErőforrásKi szabadítja fel?Példa
Felügyelt memóriaA szemétgyűjtő (GC), amikor már nincs rá hivatkozás.string, List<T>, saját osztályok.
Nem felügyelt erőforrásTe (Dispose/using); a GC csak későn és bizonytalanul.Bitmap, Graphics, Font, Pen, Brush, FileStream, adatbázis-kapcsolat, Timer, Form (ShowDialog után).
Hivatkozás, amely életben tart valamitTe (leiratkozás, törlés a gyűjteményből).Esemény-feliratkozás, static lista, gyorsítótár.
Form1.csC#
// ROSSZ: a Bitmap és a Graphics sosem szabadul fel (GDI-objektumok: korlátos számban léteznek!)
private Bitmap Bélyegkép(string ut)
{
Image eredeti = Image.FromFile(ut); // a fájl zárolva marad, amíg fel nem szabadítod
Bitmap kicsi = new Bitmap(eredeti, new Size(100, 100));

return kicsi; // az eredeti sosem szabadul fel
}

// JÓ: using: hiba esetén is felszabadít (try–finally)
private Bitmap Bélyegkép(string ut)
{
using (Image eredeti = Image.FromFile(ut))
{
return new Bitmap(eredeti, new Size(100, 100)); // az eredetit a using zárja; a visszaadott kicsit a HÍVÓ szabadítja fel
}
}

// rajzoláskor: a saját Pen, Brush, Font felszabadítása
private void panel_Paint(object sender, PaintEventArgs e)
{
using (Pen toll = new Pen(Color.Purple, 2))
using (SolidBrush ecset = new SolidBrush(Color.Lavender))
{
e.Graphics.FillRectangle(ecset, 10, 10, 100, 50);
e.Graphics.DrawRectangle(toll, 10, 10, 100, 50);
} // e.Graphics-ot NEM te szabadítod fel: a Paint kapja, a keretrendszer kezeli
}

// SAJÁT IDisposable osztály
public class Kepkezelo : IDisposable
{
private Bitmap kep;
private bool felszabaditva = false;

public void Betolt(string ut)
{
Bitmap uj = new Bitmap(ut);

if (kep != null) { kep.Dispose(); } // a régi kép felszabadítása CSERE előtt

kep = uj;
}

public void Dispose()
{
if (felszabaditva) { return; }

if (kep != null) { kep.Dispose(); kep = null; }

felszabaditva = true;
}
}
Mi az IDisposable?Az az interface, amelyet a nem felügyelt erőforrást tartó osztályok megvalósítanak: a Dispose() azonnal felszabadítja az erőforrást (nem várja a GC-t). Ha egy osztály IDisposable, mindig usingba tedd, vagy hívd a Dispose()t.
usingA using (var x = ...) { } a blokk végén (és hiba esetén is) meghívja a Dispose()-t (try–finally, 13.4 lecke). Ha az objektum a blokkon túl is kell (visszaadod), ne usingba tedd: a hívó felelős a felszabadításért.
Miért nem elég a GC?A GC a memóriát felügyeli, de a Bitmap mögött operációs rendszer szintű GDI-objektum áll, amelynek a száma folyamatonként korlátos (alapból 10 000). A GC csak később és bizonytalan időben fut: addig több ezer felszabadítatlan Bitmap gyűlhet fel, és kifogysz a GDI-objektumokból, mielőtt a memória elfogyna.
Csere előtt felszabadításHa egy mezőben tartott képet új képre cserélsz, a régit szabadítsd fel (Dispose), különben minden csere egy szivárgás. A PictureBox.Image cseréjénél is (9.8 lecke).

Jósolj!

A program minden egérmozgásnál (MouseMove) új Pen objektumot hoz létre rajzoláshoz, de nem szabadítja fel. Néhány perc rajzolás után mi történik?

Jóslat ellenőrzése

A GDI-objektumok száma folyamatosan nő (a Feladatkezelő GDI objects oszlopában követhető), míg el nem éri a 10 000-es korlátot: ekkor a rajzolás leáll vagy „Parameter is not valid” / „Out of memory” hiba jelentkezik. A GC közben lehet, hogy felszabadít egyeseket, de nem megbízhatóan. Megoldás: using, vagy a Pen egyszeri létrehozása mezőként, és a Form zárásakor felszabadítása.

A tipikus szivárgások

Szivargasok.csC#
// 1. ESEMÉNY-FELIRATKOZÁS: a hosszú életű kiadó életben tartja a feliratkozót
public static class Esemenyek { public static event EventHandler AdatValtozott; } // statikus: az egész program élete alatt él

public partial class RiportForm : Form
{
public RiportForm()
{
InitializeComponent();
Esemenyek.AdatValtozott += Frissit; // ✗ a RiportForm bezárása után is él: a statikus esemény tartja!
}

private void RiportForm_FormClosed(object sender, FormClosedEventArgs e)
{
Esemenyek.AdatValtozott -= Frissit; // ✓ leiratkozás: a Form felszabadulhat
}
}

// 2. STATIKUS GYŰJTEMÉNY / GYORSÍTÓTÁR korlát nélkül
public static class Gyorsitotar
{
public static readonly Dictionary<string, Bitmap> Kepek = new Dictionary<string, Bitmap>(); // ✗ csak nő, soha nem ürül
}

// 3. FELSZABADÍTATLAN FORM (ShowDialog után)
private void btnNyit_Click(object sender, EventArgs e)
{
BeallitasForm f = new BeallitasForm(); // ✗ nincs using / Dispose
f.ShowDialog();
}

// 4. NEM LEÁLLÍTOTT TIMER, nem felszabadított stream, nyitott kapcsolat: lásd 11.1, 13.6 lecke
Esemény: kapcsolat a kiadó és a feliratkozó köztAz a.Esemeny += b.Kezelo azt jelenti: az a objektum hivatkozást tart a b-re. Ha az a hosszú életű (statikus, főablak, szolgáltatás), a b (egy már bezárt Form) nem szabadulhat fel a GC számára: a Form és minden vezérlője a memóriában marad. Szabály: a feliratkozó leiratkozik, amikor már nem kell.
A szivárgás tüneteMinden alkalommal, amikor megnyitsz-bezársz egy ablakot, a memóriahasználat egy lépcsőnyit emelkedik, és nem csökken. Az eseménykezelő a bezárt ablak régi adataival is lefut (szellem-frissítés): kivételek (ObjectDisposedException), vagy többszörös végrehajtás.
Gyorsítótár korlát nélkülA static szótár sosem ürül, ha te nem ürítesz: korlátozott méretű gyorsítótár (legutóbb használt N elem), vagy WeakReference. Képeket tartó gyorsítótárnál a kivett képeket Dispose-olni kell.
A Form maga is erőforrásA Show() alapú ablak bezáráskor megsemmisül; a ShowDialog() ablak nem (12.1 lecke): using vagy Dispose() kell. A Form kódból létrehozott erőforrásait (Pen, Bitmap mezők, saját Timer) a FormClosed vagy a Dispose felülírása szabadítsa fel.

Jósolj!

A főablak szolgáltatása public event EventHandler Valtozott eseményt tart. Minden Riport ablak megnyitáskor feliratkozik, de nem iratkozik le. A felhasználó 200 riportablakot nyit és zár be. Hány RiportForm él a memóriában, és mit tesz egy új adatváltozás?

Jóslat ellenőrzése

Mind a 200 él (a szolgáltatás esemény-listája tartja őket), a memóriát és a GDI-objektumokat foglalják, és minden adatváltozásnál mind a 200 kezelő lefut a bezárt ablakokon (lassulás, ObjectDisposedException). Leiratkozás a FormClosed-ben megoldja.

Mérés: mérj, ne találgass

Eszközök

  • Feladatkezelő (Ctrl+Shift+Esc) → Részletek fül → oszlopok hozzáadása: Memória (privát munkakészlet), GDI-objektumok, Fogantyúk, Szálak: a program erőforrás-használata időben.
  • Visual Studio Diagnostic Tools (Debug → Windows → Show Diagnostic Tools): CPU- és memóriagörbe, Memory Usage → Take Snapshot: két pillanatkép összehasonlítása megmutatja, mely objektumok szaporodtak.
  • Stopwatch: egy művelet pontos ideje (13.7 lecke).
  • GC.GetTotalMemory(true) és Process.GetCurrentProcess().WorkingSet64: kódból olvasható memóriaérték.
  • BenchmarkDotNet: pontos mikro-mérésekhez (haladó; külön NuGet-csomag).

Mérési szabályok

  • Release fordításban mérj (a Debug lassabb, optimalizálatlan).
  • Ismételj: az első futás lassabb (JIT, gyorsítótár): a mérést többször végezd, és a mediánt nézd.
  • Előtte–utána: mérj a módosítás előtt is: különben nem tudod, javult-e.
  • Valósághű adat: 10 elemmel minden gyors; mérj a valós mérettel (10 000, 1 millió).
  • Egy módosítás egyszerre: különben nem tudod, melyik segített.
  • A szűk keresztmetszetet keresd: a program ideje általában kevés helyen megy el; azt gyorsítsd, ne mindent.
Meres.cs (using System.Diagnostics;)C#
public static string MemoriaAllapot()
{
Process p = Process.GetCurrentProcess();
long gc = GC.GetTotalMemory(false) / 1024 / 1024; // a felügyelt halom (MB); a true kényszeríti a gyűjtést mérés előtt

return "Munkakészlet: " + p.WorkingSet64 / 1024 / 1024 + " MB, felügyelt halom: " + gc + " MB, fogantyúk: " + p.HandleCount;
}

// mérés ismétléssel: medián
public static long MedianMs(Action muvelet, int ismetles = 7)
{
List<long> idok = new List<long>();

muvelet(); // bemelegítés (az első futást eldobjuk)

for (int i = 0; i < ismetles; i++)
{
Stopwatch sw = Stopwatch.StartNew();
muvelet();
sw.Stop();
idok.Add(sw.ElapsedMilliseconds);
}

idok.Sort();

return idok[idok.Count / 2];
}

Jósolj!

Egy művelet első futása 340 ms, a következő hat 45–52 ms. Melyik érték jellemzi a művelet sebességét?

Jóslat ellenőrzése

Az első futás a JIT-fordítás és a hideg gyorsítótár miatt lassabb; a művelet „állandó” sebességét a következők mutatják (kb. 48 ms, a medián). A felhasználó mégis az első futást érzi (indulás utáni első kattintás): ezért a bemelegítő futás külön adat (és lassú indulásnál érdemes a kritikus kódot indulás után háttérben előmelegíteni).

Gyorsítás: felület, adatszerkezet, algoritmus

Form1.csC#
// 1. FELÜLET: sok elem hozzáadása egyszerre (a vezérlő minden Add után újrarajzolna)
lstNagy.BeginUpdate(); // a rajzolás szünetel
for (int i = 0; i < 20000; i++) { lstNagy.Items.Add("Elem " + i); }
lstNagy.EndUpdate(); // egyetlen újrarajzolás

// vagy egyetlen hívás: AddRange
lstNagy.Items.AddRange(szovegek.ToArray());

// 2. ELRENDEZÉS: sok vezérlő létrehozásakor
pnl.SuspendLayout();
// ... 200 vezérlő hozzáadása ...
pnl.ResumeLayout(true);

// 3. VIRTUÁLIS MÓD: több százezer sor: csak a látható sorokat kéri el
lv.VirtualMode = true;
lv.VirtualListSize = adatok.Count;
lv.RetrieveVirtualItem += (s, e) => e.Item = new ListViewItem(adatok[e.ItemIndex]);

// 4. VILLOGÁS ellen: DoubleBuffered (saját rajzolt panelhez; 9.3 lecke)
typeof(Panel).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, pnlRajz, new object[] { true });

// 5. STRINGEK: összefűzés ciklusban (minden + új szöveget hoz létre: O(n²))
string s = "";
for (int i = 0; i < 50000; i++) { s += i + ","; } // ✗ lassú, sok szemét

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 50000; i++) { sb.Append(i).Append(','); } // ✓ gyors
string eredmeny = sb.ToString();

// 6. ADATSZERKEZET: keresés listában (O(n)) vagy halmazban (O(1))
bool van = nagyLista.Contains(kulcs); // ✗ minden elemet végignéz
HashSet<string> halmaz = new HashSet<string>(nagyLista);
bool vanGyorsan = halmaz.Contains(kulcs); // ✓ szinte azonnal
BeginUpdate / EndUpdateA ListBox, ListView, TreeView minden elem hozzáadása után újrarajzolná magát. A BeginUpdate és EndUpdate közt szünetel a rajzolás: 20 000 elemnél a különbség akár másodpercek és századmásodpercek közt. Ezt a mérés megmutatja.
VirtualModeSzázezer soros listánál nem hozunk létre százezer ListViewItem-et: a vezérlő csak a látható sorokat kéri el (RetrieveVirtualItem). A memóriahasználat és az indulás ideje nagyságrendekkel csökken.
O(n) és O(n²)A keresés listában minden elemet végignéz (O(n)); a HashSet/Dictionary szinte azonnal (O(1)). Egy ciklusban, 100 000 keresésnél a különbség percek és századmásodpercek közt. A string-összefűzés ciklusban O(n²): a StringBuilder O(n).
Ne a felületi szálonA hosszabb számítás a háttérbe való (11. fejezet): az ablak közben él. A gyorsítás nem csak a „gyorsabb”, hanem a „nem blokkoló” is: egy 2 mp-es, háttérben futó művelet kellemesebb, mint egy 1 mp-es, fagyasztó.

Jósolj!

Egy ciklusban 100 000 szót ellenőrzöl egy 50 000 szavas List<string> szótárban (Contains). Hány összehasonlítást végez a program nagyságrendileg, és mit javasolsz?

Jóslat ellenőrzése

Átlag 100 000 × 25 000 = kb. 2,5 milliárd összehasonlítás (másodpercek–percek). Javaslat: a szótárat egyszer HashSet<string>-be tenni (new HashSet<string>(lista, StringComparer.OrdinalIgnoreCase)): akkor 100 000 közel azonnali keresés. Mérd meg előtte és utána.

📷 Képernyőkép helye

Mit tegyél ide: a Feladatkezelő (GDI-objektumok, memória) a szivárgás-vadász futtatása közben, és a Diagnostic Tools memória-pillanatképek összehasonlítása.Javasolt fájlnév: winforms-13-9-meres.webp · Alt szöveg: A memóriahasználat mérése a Feladatkezelővel

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. „Parameter is not valid” rajzolás közben

Hosszú rajzolás után a program kivételt dob a Graphics hívásoknál.

Ok és javítás

Kifogytál a GDI-objektumokból (felszabadítatlan Pen, Brush, Bitmap). using blokk minden rajzolóobjektumra; a Feladatkezelőben figyeld a GDI-objektumok számát.

2. A memória nő, amikor ablakot nyitok-zárok

Minden riport ablak megnyitása után emelkedik a memóriahasználat.

Ok és javítás

Az ablak feliratkozott egy hosszú életű eseményre, és nem iratkozik le. -= a FormClosed-ben; a Diagnostic Tools pillanatképei mutatják a RiportForm példányok számát.

3. A kép fájlja nem törölhető

pb.Image = Image.FromFile(ut);
File.Delete(ut); // IOException: a fájlt másik folyamat használja (saját magad)
Hiba és javítás

Az Image.FromFile zárolja a fájlt, amíg a kép él. Másold memóriába: using (var tmp = Image.FromFile(ut)) { pb.Image = new Bitmap(tmp); }; a régi pb.Image-et felszabadítod.

4. A lista feltöltése másodpercekig tart

foreach (var s in szovegek) { lst.Items.Add(s); }   // 30 000 elem
Hiba és javítás

BeginUpdate/EndUpdate és AddRange, vagy virtuális mód. Mérd az előtte–utána időt.

5. A keresés lassú nagy adatnál

10 000 elemnél gyors, 500 000-nél percekig tart.

Ok és javítás

Négyzetes (O(n²)) algoritmus: egy ciklusban List.Contains vagy IndexOf. Használj HashSet/Dictionary szerkezetet; mérd a nagy adaton.

6. A GC.Collect() „megoldás”

GC.Collect();   // minden metódus végén, „hogy ne szivárogjon”
Hiba és javítás

A GC.Collect() nem szabadítja fel azt, amire még van hivatkozás (esemény, statikus lista), és lassítja a programot. A szivárgást a hivatkozás elengedése és a Dispose oldja meg; a GC.Collect() legfeljebb mérés előtt (a true paraméterrel) használható.

Mini projekt: szivárgás-vadász és gyorsító

Kapsz egy kis programot három rejtett problémával (szivárgás és lassúság). Mérd meg, javítsd, és dokumentáld az előtte–utána különbséget:

  1. Felület: btnKepek (100 bélyegkép készítése), btnRiport (Riport ablak), btnLista (Lista feltöltése (20 000)), btnSzoveg (Szöveg összefűzése (50 000)), btnKeres (Szótárkeresés), lblMeres (memória és idő), lstNaplo, Timer (1 mp-es memória-kijelzés).
  2. Kiindulás: másold be a hibás kódot (lent); egy mappában legyen legalább 5 nagyobb kép (1–3 MP).
  3. Mérés előtte: a Feladatkezelőben kövesd (Memória, GDI-objektumok, Fogantyúk): kattints 10-szer a 100 bélyegképre, majd 10 riport ablakot nyiss és zárj be; jegyezd fel az értékeket (táblázat: kezdet, 10 kattintás után).
  4. Diagnostic Tools: a riportok megnyitása-zárása előtt és után készíts memória-pillanatképet, és hasonlítsd össze: hány RiportForm él?
  5. 1. javítás (képek): using az Image.FromFile és minden Bitmap köré; a régi bélyegképek felszabadítása cserekor; mérd újra a GDI-objektumok számát.
  6. 2. javítás (esemény): a RiportForm a FormClosed-ben leiratkozik a statikus eseményről; mérd újra (a pillanatképekben nincs régi RiportForm).
  7. 3. javítás (lista, szöveg, keresés): BeginUpdate/AddRange, StringBuilder, HashSet; mérd az idejét a MedianMs segédmetódussal, Release fordításban.
  8. Jelentés: táblázat: probléma, mérés előtte, mérés utána, mit javítottam, tanulság (legalább 5 sor, számokkal).
  9. Teszteld: a javítás után a program ugyanúgy működik-e (regresszió: a bélyegképek helyesek, a riport frissül, a lista tartalma azonos); hosszú használat (100 ablaknyitás) után a memória stabil-e.
A hibás és a javított kód (próbáld ki előbb önállóan!)
Form1.cs (HIBÁS)C#
private readonly List<Bitmap> bélyegek = new List<Bitmap>();

public static class Esemenyek { public static event EventHandler AdatValtozott; public static void Jelez() { if (AdatValtozott != null) { AdatValtozott(null, EventArgs.Empty); } } }

private void btnKepek_Click(object sender, EventArgs e)
{
foreach (string ut in Directory.GetFiles(kepMappa, "*.jpg"))
{
for (int i = 0; i < 20; i++)
{
Image eredeti = Image.FromFile(ut); // nincs using: zárolt fájl, szivárgás
bélyegek.Add(new Bitmap(eredeti, new Size(120, 90))); // a lista csak nő; a régiek nem szabadulnak
}
}
}

private void btnRiport_Click(object sender, EventArgs e)
{
RiportForm f = new RiportForm(); // ShowDialog után nincs Dispose
f.ShowDialog();
}

// RiportForm:
public RiportForm() { InitializeComponent(); Esemenyek.AdatValtozott += Frissit; } // nincs leiratkozás

private void btnLista_Click(object sender, EventArgs e)
{
lstNagy.Items.Clear();
for (int i = 0; i < 20000; i++) { lstNagy.Items.Add("Elem " + i); } // minden Add újrarajzol
}

private void btnSzoveg_Click(object sender, EventArgs e)
{
string s = "";
for (int i = 0; i < 50000; i++) { s += i + ","; }
lblMeres.Text = "Hossz: " + s.Length;
}

private void btnKeres_Click(object sender, EventArgs e)
{
List<string> szotar = Enumerable.Range(0, 50000).Select(i => "szo" + i).ToList();
int talalat = 0;

for (int i = 0; i < 20000; i++) { if (szotar.Contains("szo" + (i * 7))) { talalat++; } }

lblMeres.Text = "Találat: " + talalat;
}

A javított változat az alábbi megoldás; előbb mérj és javíts magad.

A javított kód
Form1.cs (JAVÍTVA; using System.Diagnostics; using System.Text;)C#
private readonly List<Bitmap> bélyegek = new List<Bitmap>();

private void btnKepek_Click(object sender, EventArgs e)
{
foreach (Bitmap b in bélyegek) { b.Dispose(); } // a régi bélyegek felszabadítása
bélyegek.Clear();

foreach (string ut in Directory.GetFiles(kepMappa, "*.jpg"))
{
// a fájlt EGYSZER olvassuk be; a bélyeget abból készítjük (nem 20-szor); using zárja az eredetit
using (Image eredeti = Image.FromFile(ut))
{
for (int i = 0; i < 20; i++) { bélyegek.Add(new Bitmap(eredeti, new Size(120, 90))); }
}
}
}

protected override void OnFormClosed(FormClosedEventArgs e)
{
foreach (Bitmap b in bélyegek) { b.Dispose(); } // a Form zárásakor is
base.OnFormClosed(e);
}

private void btnRiport_Click(object sender, EventArgs e)
{
using (RiportForm f = new RiportForm()) { f.ShowDialog(this); } // using: ShowDialog után Dispose
}

// RiportForm:
public RiportForm() { InitializeComponent(); Esemenyek.AdatValtozott += Frissit; FormClosed += (s, e) => Esemenyek.AdatValtozott -= Frissit; } // leiratkozás

private void btnLista_Click(object sender, EventArgs e)
{
lstNagy.BeginUpdate();
lstNagy.Items.Clear();
lstNagy.Items.AddRange(Enumerable.Range(0, 20000).Select(i => (object)("Elem " + i)).ToArray());
lstNagy.EndUpdate();
}

private void btnSzoveg_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 50000; i++) { sb.Append(i).Append(','); }
lblMeres.Text = "Hossz: " + sb.Length;
}

private void btnKeres_Click(object sender, EventArgs e)
{
HashSet<string> szotar = new HashSet<string>(Enumerable.Range(0, 50000).Select(i => "szo" + i));
int talalat = 0;

for (int i = 0; i < 20000; i++) { if (szotar.Contains("szo" + (i * 7))) { talalat++; } }

lblMeres.Text = "Találat: " + talalat;
}
📷 Képernyőkép helye

Mit tegyél ide: a jelentés táblázata (előtte–utána értékek) és a Feladatkezelő a javítás után.Javasolt fájlnév: winforms-13-9-kesz.webp · Alt szöveg: A szivárgás-vadász jelentése

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. A saját projekted

Nyiss meg egy korábbi projektet, és keresd meg: hol van Bitmap, Graphics, Pen felszabadítás nélkül?

Egy lehetséges megoldás

Keresd a new Bitmap, new Pen, new SolidBrush, Image.FromFile sorokat (Ctrl+Shift+F). Mindegyiknél: van using, Dispose, vagy a felszabadítás a Form zárásakor? A rajzoló objektumokat vagy using alá, vagy mezőbe tedd, és zárjad a Formnál.

2. Gyorsítótár korláttal

Írj képgyorsítótárat, amely legfeljebb 20 képet tart, és a legrégebben használtat felszabadítja.

Egy lehetséges megoldás

Egy Dictionary<string, Bitmap> és egy LinkedList<string> a használat sorrendjéhez (LRU): elérésnél a kulcs a lista elejére, 20 fölött a lista végéről eltávolítod a kulcsot és Dispose-olod a képet. A gyorsítótár IDisposable, a Form zárásakor mindent felszabadít.

3. Mérés ismétléssel

Mérd meg a string += és a StringBuilder sebességét 1000, 10 000 és 100 000 elemnél. Mit látsz?

Egy lehetséges megoldás

Kis elemszámnál a különbség elhanyagolható; a += ideje négyzetesen nő (10× több elem → kb. 100× lassabb), a StringBuilder lineárisan. Az optimalizálás ott kell, ahol a mérés a szűk keresztmetszetet mutatja.

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. Ki szabadít fel?

Ki szabadítja fel: (a) egy string; (b) egy Bitmap?

Megoldás megnyitása

(a) a GC; (b) te (Dispose/using).

2. using

Mire jó a using?

Megoldás megnyitása

Hiba esetén is felszabadítja az IDisposable erőforrást a blokk végén (try–finally).

Közepes szint

3. Szivárgás oka

Miért nem szabadul fel egy bezárt Form, ha egy statikus eseményre feliratkozott?

Megoldás megnyitása

A statikus esemény hivatkozást tart a Form kezelőjére: a GC nem gyűjtheti, amíg van rá hivatkozás. Leiratkozás kell.

4. Mikor mérj?

Mikor optimalizálj?

Megoldás megnyitása

Ha a mérés valódi problémát mutat (lassú, nő a memória), és a szűk keresztmetszet ismert. Előtte–utána mérés, Release fordításban, valós adatmérettel.

Kihívás szint

5. WeakReference

Mire jó a WeakReference egy gyorsítótárban?

Megoldás megnyitása

Gyenge hivatkozást tart az objektumra: a GC felszabadíthatja, ha kell a memória. Gyorsítótárnál jó, ha az adat újraszámolható; a nem felügyelt erőforrást (Bitmap) tartó elemeket mégis Dispose-olni kell.

6. O(n) és O(1)

Miért gyorsabb a HashSet.Contains?

Megoldás megnyitása

A hash alapján közvetlenül a helyre ugrik (átlag O(1)), míg a List.Contains minden elemet végignéz (O(n)).

Gyors önellenőrzés

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

1. Mi a nem felügyelt erőforrás?

Az operációs rendszer szintű erőforrás (kép, ecset, fájl, kapcsolat), amelyet Dispose-zal kell felszabadítani.

2. Miért korlátos a GDI?

Folyamatonként kb. 10 000 GDI-objektum létezhet; kifogyva a rajzolás hibás.

3. Hogyan szivárog az esemény?

A hosszú életű kiadó életben tartja a feliratkozót, ha az nem iratkozik le.

4. Mivel mérsz memóriát?

Feladatkezelővel (memória, GDI, fogantyúk), a Diagnostic Tools pillanatképeivel, GC.GetTotalMemory-mel.

5. Mire jó a BeginUpdate?

Szünetelteti a vezérlő újrarajzolását sok módosítás alatt.

6. Mikor StringBuilder?

Szöveg ciklusban történő építésénél (az összefűzés négyzetesen lassul).

7. Kell-e a GC.Collect()?

Nem: nem szabadít fel hivatkozott objektumot, és lassít. A szivárgást a hivatkozás elengedése és a Dispose oldja meg.

Mit tudsz már?

  • Megkülönbözteted a felügyelt és a nem felügyelt erőforrásokat, és helyesen használod a using-ot.
  • Felismered a tipikus szivárgásokat (kép, esemény, statikus gyűjtemény, Form).
  • Módszeresen mérsz (Feladatkezelő, Diagnostic Tools, Stopwatch), előtte–utána.
  • Gyorsítod a felületet és az algoritmust a mérés alapján.

Következő lecke: Mini projekt: hibás alkalmazás javítása

A 13. fejezet záró projektje: egy hibás, lassú, szivárgó alkalmazás kivizsgálása és javítása a fejezet összes eszközével.