Grafikus C# – Windows Forms tanfolyam › 9.8 lecke Windows Forms · 9.8 lecke · erőforrás-kezelés A C# a memóriát magától kezeli (szemétgyűjtő), a grafikai objektumok mögött azonban rendszererőforrások állnak: ablakkezelők, képpont-tömbök, rajzeszközök. Ezeket a szemétgyűjtő nem látja pontosan, ezért te felelsz a felszabadításukért. Ha elmulasztod, a program lassul, egyre több memóriát eszik, végül furcsa hibákkal (A generic error occurred in GDI+, Képek és grafikus erőforrások felszabadítása
OutOfMemoryException) leáll. Ebben a leckében megtanulod a Dispose és a using szabályait, azt, hogy mit nem szabad felszabadítani, a saját osztály IDisposable megvalósítását, és hogyan mérheted a szivárgást.
A lecke végére
- tudod, mely objektumokat kell felszabadítani (
Pen,Brush,Font,Bitmap,Image,Graphics.FromImage,Region,GraphicsPath), és melyeket nem (e.Graphics,Pens,Brushes); - a
usingutasítással és (ha a projekt támogatja) ausingdeklarációval dolgozol; - a hosszú életű erőforrásokat a Form zárásakor szabadítod fel;
- saját osztályt
IDisposablefelülettel látsz el; - méred a szivárgást (memória, GDI-objektumok száma), és felismered a tüneteit;
- kezeled az
ObjectDisposedExceptionhibát, és a tulajdonjog szabályát (aki létrehozza, az szabadítja fel).
Mielőtt elkezded
Ehhez a leckéhez a 9.1–9.4 lecke (Bitmap, Graphics, Pen, Brush) és az 5.6–5.7 lecke (a Form életciklusa, FormClosed) szükséges. Az IDisposable mintát a 12. fejezet általánosabban tárgyalja.
Miért nem elég a szemétgyűjtő?
A szemétgyűjtő (garbage collector, GC) a memóriát szabadítja fel, amikor úgy ítéli, hogy szükség van rá. A Bitmap, Pen, Font, Graphics ugyanakkor egy kis felügyelt „burok” egy nem felügyelt erőforrás körül (GDI+ objektum, ablakkezelő, DC). A GC két dolgot nem tud jól:
1. Nem tudja, mekkora a mögötte lévő memória
Egy 4000 × 3000 pixeles Bitmap felügyelt része néhány tíz bájt, a nem felügyelt része viszont ~46 MB. A GC csak az előbbit látja, ezért „nem sürgős” a felszabadítás: a memória hamarabb fogy el, mint ahogy a GC lefutna.
2. Nem tudja, hány rendszerkezelő fogy
A Windows folyamatonként korlátozott számú GDI-kezelőt enged (alapból 10 000). Ha elfogy, a rajzolás hibát ad (Parameter is not valid, A generic error occurred in GDI+), vagy az ablak üres marad. A GC csak jóval később, a finalizerekkel szabadítja fel.
IDisposable felület Dispose() metódusa azonnal felszabadítja a nem felügyelt erőforrást. Aki IDisposable objektumot létrehoz, azt a használat végén Dispose-olnia kell.OutOfMemoryException vagy általános GDI+ hiba, olyan, amely „csak egy óra után” jelentkezik.Paint kezelő vagy az időzítő ismétlődően új Pen, Brush, Font, Bitmap objektumot készít, és nem szabadítja fel: pár perc alatt több tízezer objektum gyűlik össze.Jósolj!
Egy időzítő 50 ms-onként egy 1000 × 1000 pixeles Bitmapot hoz létre, és soha nem szabadítja fel; semmi nem hivatkozik rájuk. Mi történik 10 perc alatt a program memóriájával?
Jóslat ellenőrzése
A memóriahasználat gyorsan és tartósan nő (a Bitmap ~4 MB, 10 perc alatt ez 12 000 objektum, ami több gigabájt), mert a GC nem érzékeli a nem felügyelt méretet, és ritkán futtatja a finalizereket. Pontos viselkedés gépenként és verziónként eltérő, de a Dispose nélküli kód mindig kockázatos.
A Dispose és a using utasítás
// 1. kézi Dispose: hibás, ha közben kivétel keletkezik (a Dispose kimarad)
Pen toll = new Pen(Color.Black, 2);
g.DrawLine(toll, 0, 0, 100, 100);
toll.Dispose();
// 2. using utasítás: a blokk végén (kivétel esetén is) automatikusan Dispose
using (Pen toll2 = new Pen(Color.Black, 2))
{
g.DrawLine(toll2, 0, 0, 100, 100);
}
// több erőforrás egymás után: egy blokkba fűzhetők (a belső előbb szabadul fel)
using (Bitmap kep = new Bitmap(400, 300))
using (Graphics g2 = Graphics.FromImage(kep))
using (Brush ecset = new SolidBrush(Color.Gold))
{
g2.FillRectangle(ecset, 0, 0, 400, 300);
kep.Save(utvonal, System.Drawing.Imaging.ImageFormat.Png);
} // itt szabadul fel: ecset, g2, kep (fordított sorrendben)
// 3. using deklaráció (C# 8, .NET Core 3.0 / .NET 5+ projektben): a metódus végén szabadul fel
// using var toll3 = new Pen(Color.Red); // régi .NET Framework projektben nem fordul leusing (x) { ... } egy try–finally rövidítése: a finally ágban hívja az x.Dispose() metódust. Így akkor is felszabadul az erőforrás, ha a blokkban kivétel keletkezik.using esetén a legutóbb létrehozott szabadul fel elsőnek: a Graphics előbb, mint a Bitmap, amelyre rajzol. Fordítva hibát okozna.using var x = ...; rövidebb (nincs blokk), és a tartalmazó blokk (metódus) végén szabadít fel. Csak újabb nyelvi verzióban (C# 8) fordul; a régi .NET Framework projektben (0.4 lecke) a blokkos alak kell.PictureBoxnak adott kép, osztály mezője), nem szabad using blokkba tenni: akkor a mezőben tárolt objektumot az szabadítja fel, aki a mezőt birtokolja (lásd lent).Jósolj!
A következő kód a using blokkon belül jön létre a Bitmap, majd a PictureBoxnak adja át: using (Bitmap b = new Bitmap(100, 100)) { pbKep.Image = b; }. Mit lát a felhasználó?
Jóslat ellenőrzése
A PictureBox a következő rajzoláskor piros X-et (vagy kivételt) mutat: a using a blokk végén felszabadította a képet, amelyet a doboz még használ. A megjelenített kép felszabadítása a doboz (és a kép) birtokosának dolga, és csak a kicserélése után történhet (9.1 lecke).
Mit kell és mit NEM szabad felszabadítani?
| Objektum | Felszabadítsam? | Miért? |
|---|---|---|
new Pen, new SolidBrush, new Font, new StringFormat | Igen | Te hoztad létre, rendszererőforrás. |
new Bitmap, Image.FromFile, Image.FromStream | Igen | Memória és fájl-/adatfolyam-kezelő. |
Graphics.FromImage, CreateGraphics, Graphics.FromHwnd | Igen | Te kérted: rajzfelület (DC) tartozik hozzá. |
GraphicsPath, Region, Matrix, ImageAttributes | Igen | GDI+ erőforrások. |
Properties.Resources.kep | Igen (vagy tárold változóban) | Minden hívás új példányt ad. |
e.Graphics a Paint eseményben | Nem | A keretrendszeré: ő szabadítja fel. |
Pens.Black, Brushes.Red, SystemFonts, SystemColors | Nem | Közös, gyorsítótárazott objektumok (módosítani sem lehet). |
A vezérlő Font tulajdonsága (örökölt) | Nem | A vezérlő birtokolja; a saját cserélt betűt viszont te szabadítod. |
A PictureBox.Image értéke | Te | A doboz általában nem szabadítja fel a hozzárendelt képet. |
A tulajdonjog szabálya
Aki létrehozza az objektumot, az szabadítja fel, és csak akkor, amikor már senki nem használja. Ha átadsz egy képet egy másik osztálynak (PictureBox, saját osztály), előre tisztázd, ki a tulajdonos. A zavar (kétszeri felszabadítás, vagy használat felszabadítás után) a leggyakoribb hiba: ObjectDisposedException.
// a Form mezői: hosszú életű, gyakran használt erőforrások (nem készítjük újra minden Paint-ben)
private Pen racsToll = new Pen(Color.Gainsboro);
private Font cimBetu = new Font("Segoe UI", 16, FontStyle.Bold);
private Bitmap hatter;
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
racsToll.Dispose();
cimBetu.Dispose();
if (hatter != null) { hatter.Dispose(); }
if (pbKep.Image != null) { pbKep.Image.Dispose(); } // a doboz képe
}Paint-ben minden alkalommal létrehozott Font és Bitmap drága; a Pen, Brush kevésbé.FormClosed eseménybe írjuk a felszabadítást. A Designer által generált Dispose(bool) metódust ne módosítsd (a Designer.cs fájl újragenerálódik).Dispose-olni (9.1 lecke Mutat metódusa).IDisposable a legtöbb osztálynál idempotens: a második Dispose() hívás nem hibás. A null ellenőrzés viszont kell (?.Dispose() újabb nyelvi verzióban).Jósolj!
A Paint kezelő végén Brushes.Red.Dispose(); sor áll. Mi történik a következő rajzoláskor, amikor valahol újra Brushes.Red-et használsz?
Jóslat ellenőrzése
A Brushes.Red közös (gyorsítótárazott) objektum: a felszabadítása után a program hibát (ArgumentException vagy ObjectDisposedException) ad, ha valamely rajzolás újra használja. Ezért a Brushes és a Pens osztályokét soha ne szabadítsd fel.
Saját osztály IDisposable felülettel
Ha egy osztályod IDisposable mezőt tart (Bitmap, Pen), akkor az osztály is IDisposable legyen: a Dispose a belső erőforrásokat szabadítja fel. Így a használó a megszokott módon (using) kezelheti. A 9.6–9.7 lecke rajzoló modelljének pufferképe például így nézhet ki:
public class RajzModell : IDisposable
{
private Bitmap vaszon;
private Pen toll;
private bool felszabaditva = false;
public RajzModell(int szelesseg, int magassag)
{
vaszon = new Bitmap(szelesseg, magassag);
toll = new Pen(Color.Black, 2);
using (Graphics g = Graphics.FromImage(vaszon)) { g.Clear(Color.White); }
}
public void Vonal(Point a, Point b)
{
EllenorizFelszabaditas(); // felszabadított objektum használata: érthető hiba
using (Graphics g = Graphics.FromImage(vaszon))
{
g.DrawLine(toll, a, b);
}
}
public Bitmap Kep { get { EllenorizFelszabaditas(); return vaszon; } }
private void EllenorizFelszabaditas()
{
if (felszabaditva)
{
throw new ObjectDisposedException(nameof(RajzModell));
}
}
public void Dispose()
{
if (felszabaditva) { return; } // kétszeri hívás ártalmatlan
felszabaditva = true;
if (toll != null) { toll.Dispose(); toll = null; }
if (vaszon != null) { vaszon.Dispose(); vaszon = null; }
}
}
// használat: a Formban mezőként, a FormClosed-ben Dispose; vagy rövid életű using blokkban
using (RajzModell modell = new RajzModell(400, 300))
{
modell.Vonal(new Point(0, 0), new Point(100, 100));
modell.Kep.Save("rajz.png");
}IDisposable mezők, nincs saját nem felügyelt erőforrás) a Dispose() a mezők Dispose hívása, finalizer nélkül. A teljes „Dispose-minta” (Dispose(bool), finalizer) csak közvetlen nem felügyelt erőforrásnál kell.felszabaditva jelző megakadályozza a kétszeri felszabadítást. A mezők null értékre állítása a további használatot hibássá teszi (érthető hibával, ha ellenőrzöd).ObjectDisposedException kivételt dob. A saját osztályodban is ezt érdemes dobni, hogy a hiba érthető legyen.FormClosed-ben hívd a Dispose() metódust. Ez az „aki létrehozza, szabadítja” szabály alkalmazása.Jósolj!
A RajzModell Dispose() metódusát meghívod, majd még egyszer meghívod, és utána a modell.Kep tulajdonságot olvasod. Mi történik?
Jóslat ellenőrzése
A második Dispose() ártalmatlan (a jelző miatt nem csinál semmit), a Kep olvasása viszont ObjectDisposedException kivételt dob (érthető üzenettel: a RajzModell felszabadított objektum).
A szivárgás mérése
A szivárgás gyanúját mérni lehet: (1) a Feladatkezelőben (Részletek fül, oszlopok: Memória (privát munkakészlet), GDI-objektumok), vagy (2) a saját programból, kiírva az állapotsorba:
// a folyamat GDI-objektumainak száma (Windows API): 0 = GDI objektumok, 1 = USER objektumok
[DllImport("user32.dll")]
private static extern uint GetGuiResources(IntPtr folyamat, uint jelzo);
private int GdiObjektumok()
{
using (Process p = Process.GetCurrentProcess())
{
return (int)GetGuiResources(p.Handle, 0);
}
}
private long PrivatMemoriaMB()
{
using (Process p = Process.GetCurrentProcess())
{
p.Refresh();
return p.PrivateMemorySize64 / (1024 * 1024);
}
}
private void tmrMeres_Tick(object sender, EventArgs e)
{
tsslMeres.Text = "Memória: " + PrivatMemoriaMB() + " MB GDI-objektumok: " + GdiObjektumok();
}
// a finalizerek kényszerítése: csak kísérletnek, a valós programban NEM megoldás!
GC.Collect();
GC.WaitForPendingFinalizers();GetGuiResources a folyamat által birtokolt GDI-kezelők számát adja (a Feladatkezelő „GDI-objektumok” oszlopa). Normális program néhány száz; ha percek alatt ezrekre nő, szivárgás van (a határ alapból 10 000). A CreateGraphics() felszabadítás nélkül, a GetHbitmap() és az ikonok kezelői jól látszanak itt.PrivateMemorySize64 a folyamat privát memóriáját adja. A felszabadítatlan Bitmapok miatt ez nő. A számok gépenként és .NET-verziónként eltérnek, a trend (nő-e folyamatosan) az érdekes.GC.Collect() és a GC.WaitForPendingFinalizers() kísérletre jó: megmutatja, hogy a finalizerek lefutása után a memória csökken. Éles programban nem javítja a hibát: a Dispose hívás a megoldás.DllImport a Windows beépített függvényét hívja (a „platform meghívás”): haladó téma, itt csak mérésre használjuk. Más operációs rendszeren (a .NET MAUI, 15. fejezet) nem működik.Jósolj!
Egy gombnyomás 3000-szer meghívja a pnlRajz.CreateGraphics() metódust Dispose nélkül. Mit mutat a GDI-objektumok száma, és mi történik, ha a kód 10 000 hívásig futna?
Jóslat ellenőrzése
A GDI-objektumok száma ezrekkel nő (minden CreateGraphics egy DC-t foglal). A 10 000-es határnál a rajzolás meghibásodik (általános GDI+ hiba, vagy a vezérlők üresen maradnak). A finalizerek lefutásáig a kezelők nem szabadulnak fel; a pontos viselkedés a rendszeren múlik.
Mit tegyél ide: a szivárgásvadász program a mérőszámokkal és a gombokkal, valamint a Feladatkezelő „GDI-objektumok” oszlopa.Javasolt fájlnév: winforms-9-8-szivargas.webp · Alt szöveg: A szivárgásvadász 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. A program memóriája folyamatosan nő
private void tmrAnim_Tick(object sender, EventArgs e)
{
pbKep.Image = new Bitmap(400, 300); // a régi kép soha nem szabadul fel
}Hiba és javítás
A régi kép felszabadítása hiányzik. Őrizd meg a régit, állítsd be az újat, majd régi.Dispose() (lásd a 9.1 lecke Mutat metódusát).
2. Piros X a képen
A Valahol felszabadítottad azt a képet, amelyet a doboz még használ (PictureBox képe hirtelen piros X-szel jelenik meg.Ok és javítás
using blokk vagy kézi Dispose). A megjelenített képet ne szabadítsd fel; csak a kicseréltet.
3. ArgumentException a Brushes használatakor
using (Brush b = Brushes.Red) // a közös ecset használata using-gal
{
g.FillRectangle(b, 0, 0, 50, 50);
}Hiba és javítás
A Brushes.Red közös: a using felszabadítaná. Közvetlenül használd (g.FillRectangle(Brushes.Red, ...)), vagy hozz létre saját ecsetet: new SolidBrush(Color.Red).
4. A fájl nem törölhető a kép megjelenítése után
A képfájl zárolva marad. Az Ok és javítás
Image.FromFile képet nem szabadítod fel (vagy a program a fájlból tölti közvetlenül). Töltsd másolattal (new Bitmap(forras)) és szabadítsd a forrást (9.1 lecke).
5. Image.FromStream után a kép elromlik
using (MemoryStream ms = new MemoryStream(bajtok))
{
kep = Image.FromStream(ms); // a kép az adatfolyamra támaszkodik
} // az adatfolyam lezárult: a kép használata hibásHiba és javítás
Az Image.FromStream által adott képnek az adatfolyam a kép teljes élettartama alatt nyitva kell maradnia. Másolatot készíts a blokkon belül: kep = new Bitmap(Image.FromStream(ms));, a közbülső képet felszabadítva.
6. Kétszeri felszabadítás kivételt dob
A program Ugyanazt az objektumot két helyen szabadítod fel, vagy már felszabadított objektumot használsz. Tisztázd a tulajdonost; a saját osztály ObjectDisposedException kivétellel áll le zárás közben.Ok és javítás
Dispose metódusa legyen idempotens, és ellenőrizd a használatot.
Mini projekt: szivárgásvadász
Készíts kísérleti programot, amellyel látod a szivárgás hatását, és a helyes mintát:
- Mérés: az
tmrMeres(500 ms) kiírja az állapotsorba a privát memóriát (MB) és a GDI-objektumok számát (a leckeGdiObjektumokésPrivatMemoriaMBmetódusa); egy kisChart-szerű vonal a mérések történetéhez (List<long>, a panelPainteseményében rajzolva, 9.9 lecke előkészítése). - 1. kísérlet (Bitmap): 100 Bitmap felszabadítás nélkül és 100 Bitmap using-gal gomb (1000 × 1000 pixel, mindegyikre egy vonal rajzolva); a mérés mutatja a különbséget; a GC.Collect gomb megmutatja, hogy a finalizerek lefutása után csökken a memória.
- 2. kísérlet (GDI-kezelők): 1000 CreateGraphics felszabadítás nélkül és using-gal gomb; a GDI-objektumok számának változása.
- 3. kísérlet (PictureBox): a Képcsere helyesen és Képcsere szivárgással gomb 200 képet cserél egymás után a
pbKepdobozban (a helyes: az új beállítása után a régiDispose). - 4. kísérlet (ObjectDisposedException): a Használat felszabadítás után gomb a
RajzModellfelszabadítása után hívja aKeptulajdonságot; a kivétel érthető üzenettel látszik az állapotsorban (nem fagy le). - Szabálylista: a Formon egy olvasható „Felszabadítási szabályok” panel (aki létrehozza, szabadítja;
using;e.Graphics,Pens,Brushesnem; régi kép a csere után; hosszú életű erőforrás aFormClosed-ben). - Biztonság: a kísérletek gombjai a futás alatt letiltottak; a nagy memóriás kísérletek mennyisége korlátozott (hogy ne fogyjon el a gép memóriája); a Visszaállítás gomb a
GC.Collect()hívással tisztít. - Teszteld: minden kísérlet előtt és után a mérőszámok, a Feladatkezelő oszlopai, a helyes és a hibás változat különbsége, a program stabilitása.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A kísérletek a kódban rövid, ismétlődő ciklusok. A „szivárgó” változat szándékosan hibás kód (csak a kísérlethez!): a valós programban soha nem írunk ilyet.
[DllImport("user32.dll")]
private static extern uint GetGuiResources(IntPtr folyamat, uint jelzo);
private List<long> elozmeny = new List<long>();
private int GdiObjektumok()
{
using (Process p = Process.GetCurrentProcess()) { return (int)GetGuiResources(p.Handle, 0); }
}
private long PrivatMemoriaMB()
{
using (Process p = Process.GetCurrentProcess())
{
p.Refresh();
return p.PrivateMemorySize64 / (1024 * 1024);
}
}
private void tmrMeres_Tick(object sender, EventArgs e)
{
long mb = PrivatMemoriaMB();
elozmeny.Add(mb);
if (elozmeny.Count > 120) { elozmeny.RemoveAt(0); }
tsslMeres.Text = "Memória: " + mb + " MB GDI-objektumok: " + GdiObjektumok();
pnlGrafikon.Invalidate();
}
private void pnlGrafikon_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
if (elozmeny.Count < 2) { return; }
long max = Math.Max(1, elozmeny.Max());
PointF[] pontok = new PointF[elozmeny.Count];
for (int i = 0; i < elozmeny.Count; i++)
{
pontok[i] = new PointF(i * (pnlGrafikon.Width - 1f) / (elozmeny.Count - 1), pnlGrafikon.Height - 1 - elozmeny[i] * (pnlGrafikon.Height - 2f) / max);
}
using (Pen toll = new Pen(Color.SteelBlue, 2)) { g.DrawLines(toll, pontok); }
}
private void Kiserlet(Action munka)
{
pnlKiserletek.Enabled = false;
try { munka(); }
finally { pnlKiserletek.Enabled = true; }
}
private void btnBitmapSzivarog_Click(object sender, EventArgs e)
{
Kiserlet(() =>
{
for (int i = 0; i < 100; i++)
{
Bitmap b = new Bitmap(1000, 1000); // SZÁNDÉKOSAN hibás: nincs Dispose
using (Graphics g = Graphics.FromImage(b)) { g.DrawLine(Pens.Black, 0, 0, 999, 999); }
}
});
}
private void btnBitmapUsing_Click(object sender, EventArgs e)
{
Kiserlet(() =>
{
for (int i = 0; i < 100; i++)
{
using (Bitmap b = new Bitmap(1000, 1000))
using (Graphics g = Graphics.FromImage(b))
{
g.DrawLine(Pens.Black, 0, 0, 999, 999);
}
}
});
}
private void btnGdiSzivarog_Click(object sender, EventArgs e)
{
Kiserlet(() =>
{
for (int i = 0; i < 1000; i++) { Graphics g = pnlGrafikon.CreateGraphics(); } // SZÁNDÉKOSAN hibás
});
}
private void btnGdiUsing_Click(object sender, EventArgs e)
{
Kiserlet(() =>
{
for (int i = 0; i < 1000; i++) { using (Graphics g = pnlGrafikon.CreateGraphics()) { } }
});
}
private void btnGc_Click(object sender, EventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
private void KepCsere(bool helyes)
{
for (int i = 0; i < 200; i++)
{
Bitmap uj = new Bitmap(600, 400);
using (Graphics g = Graphics.FromImage(uj)) { g.Clear(Color.FromArgb(i, 255 - i, 128)); }
Image regi = pbKep.Image;
pbKep.Image = uj;
if (helyes && regi != null) { regi.Dispose(); }
}
}
private void btnKepHelyes_Click(object sender, EventArgs e) { Kiserlet(() => KepCsere(true)); }
private void btnKepSzivarog_Click(object sender, EventArgs e) { Kiserlet(() => KepCsere(false)); }
private void btnFelszabaditott_Click(object sender, EventArgs e)
{
RajzModell m = new RajzModell(100, 100);
m.Dispose();
try
{
Bitmap b = m.Kep;
}
catch (ObjectDisposedException ex)
{
tsslMeres.Text = "ObjectDisposedException: " + ex.ObjectName;
}
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (pbKep.Image != null) { pbKep.Image.Dispose(); }
}Mit tegyél ide: a szivárgásvadász a memória-grafikonnal: a szivárgó kísérlet után emelkedő, a using után lapos vonal.Javasolt fájlnév: winforms-9-8-kesz.webp · Alt szöveg: A szivárgásvadász és a memóriagrafikon
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. Gyorsítótárazott tollak
Írj Szótár (EszkozTar osztályt, amely a kért színű és vastagságú tollat egyszer hozza létre, és újra visszaadja, a Dispose pedig mindet felszabadítja. Melyik adatszerkezetet használod?Egy lehetséges megoldás
Dictionary<string, Pen>), a kulcs a szín és a vastagság; a Dispose végigmegy az értékeken (a 9.4 lecke TollGyar osztálya).
2. Bélyegképek felszabadítása
Egy mappa képeit bélyegképként mutatod egy Végigmész a panel vezérlőin visszafelé: a FlowLayoutPanelben. A mappa váltásakor mit kell tenned a régi bélyegképekkel?Egy lehetséges megoldás
PictureBox.Image értékét Dispose-olod, magát a vezérlőt is (Controls.Remove, Dispose), és csak utána töltöd fel újra.for (int i = flpKepek.Controls.Count - 1; i >= 0; i--){ PictureBox pb = flpKepek.Controls[i] as PictureBox; flpKepek.Controls.RemoveAt(i); if (pb != null) { if (pb.Image != null) { pb.Image.Dispose(); } pb.Dispose(); }}
3. Hibabiztos using
Egy módszer három erőforrást nyit meg, és a harmadik létrehozása hibázhat. Biztosítja-e a Igen: az egymásba fűzött using az első kettő felszabadítását?Egy lehetséges megoldás
using blokkokban az első kettő már létrejött, és a kivétel esetén is felszabadul (a finally miatt). A kézi Dispose hívások ezt nem biztosítják.
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. Felszabadítsam?
Döntsd el: (a) (a) igen; (b) nem; (c) nem; (d) igen.new Font(...); (b) e.Graphics; (c) Pens.Black; (d) Graphics.FromImage(b).Megoldás megnyitása
2. using átírása
Írd át A fordító ezt generálja:try–finally alakra: using (Pen p = new Pen(Color.Red)) { g.DrawLine(p, 0, 0, 5, 5); }.Megoldás megnyitása
Pen p = new Pen(Color.Red);try{ g.DrawLine(p, 0, 0, 5, 5);}finally{ if (p != null) { p.Dispose(); }}
Közepes szint
3. Ki a tulajdonos?
Egy metódus Ezt meg kell állapodni és dokumentálni: leggyakrabban az átvevő osztály (amely tartja) a tulajdonos, és az Bitmapot készít, és egy másik osztály konstruktorának adja át, amely mezőben tárolja. Ki szabadítja fel?Megoldás megnyitása
IDisposable megvalósításával szabadítja fel. A hívó ilyenkor nem Dispose-ol. Ha az átvevő csak használja, a hívó marad a tulajdonos.
4. Hosszú életű vagy rövid életű?
Hol hozod létre: (a) a rács tollát (mindig ugyanaz); (b) az aktuális alakzat tollát (más a színe); (c) a háttérképet? (a) mezőben, a Megoldás megnyitása
FormClosed-ben szabadítva; (b) using blokkban a Paint-ben (vagy gyorsítótárban); (c) mezőben, és a méretváltozáskor újra készítve (a régi felszabadítva).
Kihívás szint
5. Finalizer megfigyelése
Írj kis osztályt A finalizer nem kiszámítható időben fut (a ~Megfigyelo() finalizerrel, amely kiírja, hogy lefutott, és mutasd meg, mikor fut le a GC.Collect() után. Mit tanulsz belőle?Megoldás megnyitása
GC.Collect hívásig vagy később); ezért nem szabad rá bízni a rendszererőforrás időbeni felszabadítását. Ez a Dispose indoka.public class Megfigyelo{ ~Megfigyelo() { System.Diagnostics.Debug.WriteLine("A finalizer lefutott."); }}// használatnew Megfigyelo(); // azonnal elérhetetlenGC.Collect();GC.WaitForPendingFinalizers();
6. WeakReference a gyorsítótárban
Egy képgyorsítótár a nem használt képeket felszabadíthatja, ha a memória fogy. Mit használsz az objektum élettartamának lazításához? A Megoldás megnyitása
WeakReference gyenge hivatkozás: a GC felszabadíthatja az objektumot, ha más nem hivatkozik rá; a gyorsítótár ilyenkor újra betölti. A Bitmap Dispose hívását ilyenkor sem lehet elhagyni, ha a kép tulajdonosa te vagy, ezért a megoldás összetett; legtöbbször a korlátozott méretű LRU gyorsítótár egyszerűbb (haladó téma).
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A GC a nem felügyelt erőforrásokat (GDI-kezelő, kép memória) későn és kiszámíthatatlanul szabadítja fel; a A blokk végén (kivétel esetén is) meghívja a Az Aki létrehozza, az szabadítja fel, amikor már senki nem használja; átadáskor tisztázni kell. Az új kép beállítása után. A Feladatkezelő (Memória, GDI-objektumok) vagy a programból a Hiba, amelyet a felszabadított objektum használata vált ki.1. Miért kell Dispose, ha van szemétgyűjtő?
Dispose azonnal.2. Mit csinál a using utasítás?
Dispose metódust (try–finally).3. Mit nem szabad felszabadítani?
e.Graphics, a Pens, Brushes, SystemFonts, SystemColors közös objektumait.4. Mi a tulajdonjog szabálya?
5. Mikor szabadítod fel a régi képet?
6. Hogyan mérsz szivárgást?
PrivateMemorySize64 és a GetGuiResources; a trend számít.7. Mi az ObjectDisposedException?
Mit tudsz már?
- Tudod, miért nem elég a szemétgyűjtő, és mikor kell
Dispose. - A
usingutasítással dolgozol, és tudod, mit nem szabad felszabadítani. - A hosszú életű erőforrásokat a
FormClosed-ben szabadítod fel. - Saját osztályt
IDisposablefelülettel látsz el. - Méred a szivárgást, és felismered a tüneteit.
Következő lecke: Grafikonok és adatok szemléltetése
Adatok vizualizálása saját rajzolással: oszlop-, vonal- és kördiagram, tengelyek, jelmagyarázat, és a beépített Chart vezérlő.




