Grafikus C# – Windows Forms tanfolyam › 10.9 lecke Windows Forms · 10.9 lecke · megbízható mentés A Mentési hibák, fájlzárak és kivételkezelés
try–catch alapjait már ismered (4.7 lecke), és minden eddigi fájlműveletnél használtad is. De a megbízható mentés ennél több: ha a program mentés közben összeomlik, megtelik a lemez vagy elmegy az áram, a felhasználó nem veszítheti el sem az új, sem a régi adatait. Ebben a leckében a fájlkivételek valódi okait (zárolt fájl, írásvédett fájl, megtelt lemez, jogosultság, hosszú útvonal) és azok érthető üzenetté alakítását tanulod, majd a biztonságos mentés mintáját: ideiglenes fájlba írás, File.Replace és biztonsági másolat, betöltés tartalékból, újrapróbálkozás zárolt fájlnál.
A lecke végére
- ismered a fájlkivételek családját, és a
catchblokkok helyes sorrendjét (szűkebb előbb); - felismered a zárolt fájl, a megtelt lemez, az írásvédettség és a jogosultság hibáját, és érthető üzenetet adsz (mit történt, miért, mit tegyen a felhasználó);
- biztonságosan mentesz: ideiglenes fájl,
Flush(true),File.Replace, biztonsági másolat; - betöltéskor a sérült fájl helyett a tartalék másolatot ajánlod fel;
- újrapróbálkozást írsz átmeneti zárnál (
whenkivétel-szűrővel), és tudod, mikor nem szabad újrapróbálni; - a hibát naplózod, és soha nem nyeled el csendben.
Mielőtt elkezded
Ehhez a leckéhez a 4.7 lecke (try, catch, finally), a 10.1–10.2 lecke (File, FileStream, FileShare) és a 10.6 lecke (hibás fájl nem írhatja felül az épet) szükséges.
A fájlkivételek családja és okaik
| Kivétel | Tipikus ok | Újrapróbálható? |
|---|---|---|
FileNotFoundException | A fájl nem létezik (vagy időközben törölték). | Nem |
DirectoryNotFoundException | A mappa nem létezik (törölt mappa, lecsatolt meghajtó). | Nem (a mappát létre lehet hozni) |
UnauthorizedAccessException | Nincs jogosultság; írásvédett fájl; a cél egy mappa; a rendszermappa. | Nem |
PathTooLongException | Az útvonal túl hosszú (régi API-nál ~260 karakter). | Nem |
IOException (általános) | Zárolt fájl (másik folyamat használja), megtelt lemez, hálózati hiba, hibás fájlrendszer. | Zár: igen; lemez: nem |
DriveNotFoundException | A meghajtó nem elérhető (pendrive kihúzva). | Nem |
NotSupportedException, ArgumentException | Érvénytelen útvonalformátum (például C:\a:b, érvénytelen karakterek). | Nem (programhiba vagy hibás bemenet) |
// A catch blokkok sorrendje: a SZŰKEBB (specifikus) típus ELŐL, az általános (IOException) mögötte.
// FileNotFoundException és DirectoryNotFoundException az IOException leszármazottja:
// ha az általános áll elöl, a szűkebb ág sosem fut (a fordító hibát jelez).
try
{
File.WriteAllText(ut, tartalom);
}
catch (UnauthorizedAccessException) { /* jogosultság, írásvédett */ }
catch (DirectoryNotFoundException) { /* hiányzó mappa */ }
catch (PathTooLongException) { /* hosszú útvonal */ }
catch (IOException ex) { /* zár, lemez, hálózat: az ok a HResult-ból */ }
catch (ArgumentException) { /* érvénytelen útvonal */ }
catch (NotSupportedException) { /* érvénytelen formátum */ }
// az IOException okának azonosítása a Windows hibakódból (a HResult alsó 16 bitje)
private static bool ZarHiba(IOException ex)
{
int kod = ex.HResult & 0xFFFF;
return kod == 32 || kod == 33; // 32: ERROR_SHARING_VIOLATION, 33: ERROR_LOCK_VIOLATION
}
private static bool LemezTele(IOException ex)
{
int kod = ex.HResult & 0xFFFF;
return kod == 39 || kod == 112; // 39: ERROR_HANDLE_DISK_FULL, 112: ERROR_DISK_FULL
}catch blokkokat fentről lefelé értékeli a program, és az első egyezőt hajtja végre. Az IOException a FileNotFoundException őse: ha az áll elöl, a szűkebb sosem fut. A fordító jelzi (a korábbi catch már kezeli).IOException sok különböző hibát takar. Az üzenet szövege nyelvfüggő, nem alkalmas döntésre; a HResult Windows-hibakódja pontos: 32 (a fájlt másik folyamat használja), 112 (megtelt a lemez).IOException leszármazott! Az IOException catch nem fogja el. Írásvédett fájl írásakor, mappára írási kísérletnél és jogosultság hiányánál is ez érkezik.catch (Exception) a programhibákat is elnyeli (NullReferenceException), és elfedi őket. Csak a legfelső szinten (naplózásra és biztonságos leállításra) használd, a fájlműveleteknél a várt fájlkivételeket.Jósolj!
A kód catch (IOException) blokkal védi a File.WriteAllText hívást. A cél fájl írásvédett. Elkapja-e az IOException blokk a hibát?
Jóslat ellenőrzése
Nem: az írásvédett fájl írása UnauthorizedAccessException kivételt dob, amely nem az IOException leszármazottja. A program leáll, ha nincs külön catch (UnauthorizedAccessException) ág.
Érthető hibaüzenet: mit, miért, mit tegyek
A felhasználónak nem a kivétel neve (IOException) vagy a rendszer angol üzenete kell, hanem három dolog: mi nem sikerült, miért, mit tehet. Egy központi metódus alakítja a kivételt ilyen szöveggé, így minden fájlművelet egységes üzenetet ad:
public static class FajlHiba
{
// a kivételből érthető üzenet; az ujraProbalhato jelzi, érdemes-e a „Újra” gombot felkínálni
public static string Szoveg(Exception ex, string muvelet, string fajl, out bool ujraProbalhato)
{
ujraProbalhato = false;
string nev = Path.GetFileName(fajl);
if (ex is UnauthorizedAccessException)
{
return muvelet + " nem sikerült: nincs jogosultság a(z) " + nev + " fájlhoz, vagy a fájl írásvédett. Válassz másik helyet, vagy szüntesd meg az írásvédelmet.";
}
if (ex is DirectoryNotFoundException || ex is DriveNotFoundException)
{
return muvelet + " nem sikerült: a mappa vagy a meghajtó nem érhető el (kihúzott pendrive?). Ellenőrizd a helyet, és próbáld újra.";
}
if (ex is FileNotFoundException) { return muvelet + " nem sikerült: a(z) " + nev + " fájl nem található (időközben törölték vagy áthelyezték)."; }
if (ex is PathTooLongException) { return muvelet + " nem sikerült: az útvonal túl hosszú. Válassz rövidebb nevet vagy kevésbé mély mappát."; }
IOException io = ex as IOException;
if (io != null)
{
int kod = io.HResult & 0xFFFF;
if (kod == 32 || kod == 33)
{
ujraProbalhato = true;
return muvelet + " nem sikerült: a(z) " + nev + " fájlt másik program használja. Zárd be, majd próbáld újra.";
}
if (kod == 39 || kod == 112) { return muvelet + " nem sikerült: nincs elég szabad hely a lemezen. Szabadíts fel helyet, vagy válassz másik meghajtót."; }
ujraProbalhato = true;
return muvelet + " nem sikerült (" + ex.Message + "). Ellenőrizd a meghajtót, és próbáld újra.";
}
return muvelet + " közben váratlan hiba történt: " + ex.Message;
}
}FajlHiba.Szoveg metódus miatt minden fájlműveletnél azonos a megfogalmazás, és egy helyen javítható. A kivétel eredeti szövegét csak az utolsó (általános) esetben mutatjuk.ex.ToString() a hívási lánccal). Írd naplófájlba (10.2 lecke), hogy a hiba később kivizsgálható legyen.catch { } elrejti a hibát: a felhasználó azt hiszi, hogy a mentés sikerült. Ha nem tudsz érdemben reagálni, legalább naplózz, és jelezd.Jósolj!
A mentés catch (Exception) { } üres blokkal védett. A lemez megtelt, a mentés nem sikerült. Mit lát a felhasználó?
Jóslat ellenőrzése
Semmit: a program „elmentettnek” jelzi a munkát (vagy nem jelez hibát), és a felhasználó a program bezárásakor elveszíti az adatait. Az elnyelt hiba a legrosszabb: a hibát jelezni kell, és a „módosított” jelzőt meg kell tartani.
Biztonságos mentés: ideiglenes fájl és csere
Ha a File.WriteAllText közvetlenül a célfájlba ír, és a mentés félúton megszakad (összeomlás, áramszünet, megtelt lemez), a fájl félig megírt vagy üres marad: a régi és az új adat is elveszett. A biztonságos minta:
- az új tartalmat ideiglenes fájlba írod, ugyanabban a mappában (azonos meghajtó);
Flush(true): az adat ténylegesen a lemezre kerül (nem csak a rendszer gyorsítótárába);- a
File.Replace(ideiglenes, cél, biztonsagiMasolat)egy lépésben cseréli az újat a régire, és a régit.baknéven megtartja; - ha bármi hibázik, a régi fájl érintetlen, és az ideiglenes fájl törlődik.
public static class BiztonsagosFajl
{
public static void Ment(string ut, string tartalom)
{
string mappa = Path.GetDirectoryName(Path.GetFullPath(ut));
Directory.CreateDirectory(mappa);
string ideiglenes = Path.Combine(mappa, Path.GetFileName(ut) + "." + Guid.NewGuid().ToString("N") + ".tmp");
try
{
using (FileStream fs = new FileStream(ideiglenes, FileMode.CreateNew, FileAccess.Write, FileShare.None))
using (StreamWriter iro = new StreamWriter(fs, new UTF8Encoding(false)))
{
iro.Write(tartalom);
iro.Flush(); // a StreamWriter puffere a FileStream-be
fs.Flush(true); // a lemezre (az operációs rendszer gyorsítótárából is)
}
if (File.Exists(ut)) { File.Replace(ideiglenes, ut, ut + ".bak"); } // csere + a régi biztonsági másolata
else { File.Move(ideiglenes, ut); }
}
finally
{
try { if (File.Exists(ideiglenes)) { File.Delete(ideiglenes); } } // hiba esetén nem marad szemét
catch (IOException) { }
}
}
// betöltés: előbb a főfájl, hiba (sérült) esetén a .bak másolat; a hívó eldönti, mit tekint sérültnek
public static string Betolt(string ut, Func<string, bool> ervenyes, out bool tartalekbol)
{
tartalekbol = false;
foreach (string jelolt in new[] { ut, ut + ".bak" })
{
if (!File.Exists(jelolt)) { continue; }
try
{
string szoveg = File.ReadAllText(jelolt, Encoding.UTF8);
if (ervenyes(szoveg)) { tartalekbol = jelolt != ut; return szoveg; }
}
catch (IOException) { } // a következő jelölt
}
return null; // egyik sem használható
}
}File.Replace (és a gyors átnevezés) csak ugyanazon a meghajtón egyetlen, megszakíthatatlan lépés. Másik meghajtóra az áthelyezés másolás + törlés (megszakadhat). Az ideiglenes fájl a célfájl mellé kerül.Flush() a .NET pufferét üríti, a Flush(true) az operációs rendszer lemezgyorsítótárát is. Áramszünetnél csak így biztos, hogy az adat a lemezen van. Kicsit lassabb, mentésnél érdemes.File.Copy(cel, bak, true), majd File.Copy(ideiglenes, cel, true)..bak egy generációnyi visszaút: ha a főfájl a mentés után sérültnek bizonyul (rossz JSON), a betöltés a tartalékból olvas, és jelzi a felhasználónak.Jósolj!
A biztonságos mentés közben a iro.Write(tartalom) hívás kivételt dob (megtelt a lemez). Mi van a célfájlban, és mi az ideiglenes fájllal?
Jóslat ellenőrzése
A célfájl érintetlen (a régi, ép tartalom), az ideiglenes fájl a finally miatt törlődik. A kivétel a hívóhoz kerül, és az érthető üzenet jelenik meg. Közvetlen WriteAllText esetén a célfájl csonka lenne.
Mit tegyél ide: a mentő-labor: a naplólista a szimulált hibákkal (zár, írásvédett, megszakadt mentés) és az érthető üzenetekkel.Javasolt fájlnév: winforms-10-9-mento-labor.webp · Alt szöveg: A biztonságos mentő-labor
Újrapróbálkozás átmeneti zárnál
A zárolt fájl gyakran átmeneti: a víruskereső, a felhőszinkronizáló vagy a keresőindexelő néhány tized másodpercig fogja a fájlt. Ilyenkor érdemes néhányszor, rövid szünettel újrapróbálni. Az újrapróbálás csak az átmeneti hibákra való: jogosultság, megtelt lemez, hiányzó mappa nem javul magától:
// legfeljebb darab próbálkozás, a szünet minden körben nő; csak az átmeneti (zár) hibákra
public static void Ujraproba(Action muvelet, int darab = 3, int szunetMs = 200)
{
for (int i = 1; ; i++)
{
try
{
muvelet();
return;
}
catch (IOException ex) when (ZarHiba(ex) && i < darab) // kivétel-szűrő: csak a zár hibára, és amíg van próba
{
Thread.Sleep(szunetMs * i); // 200, 400, 600 ms... (a felületi szálat blokkolja: csak rövid ideig!)
}
// az utolsó próba hibája, vagy más hiba: továbbmegy a hívóhoz
}
}
// használat
try
{
BiztonsagosFajl.Ujraproba(() => BiztonsagosFajl.Ment(ut, tartalom));
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
bool ujra;
string uzenet = FajlHiba.Szoveg(ex, "A mentés", ut, out ujra);
Naploz(ex.ToString()); // a teljes kivétel a naplóba
if (MessageBox.Show(this, uzenet + (ujra ? Environment.NewLine + Environment.NewLine + "Újrapróbálod?" : ""), "Mentés", ujra ? MessageBoxButtons.RetryCancel : MessageBoxButtons.OK, MessageBoxIcon.Warning) == DialogResult.Retry)
{
MentGomb(); // újra
}
}catch (...) when (feltétel) csak akkor fogja el a kivételt, ha a feltétel igaz; különben úgy viselkedik, mintha a catch nem is létezne. Ideális az „csak a zár hibát” megfogalmazására.Thread.Sleep a felületi szálat blokkolja: néhány tized másodpercig elfogadható, hosszabb várakozáshoz a 11. fejezet async megoldása kell.MessageBoxButtons.RetryCancel (Újra / Mégse) lehetőséget ad: a felhasználó bezárhatja a zároló programot, és újrapróbálhatja.Jósolj!
Az Ujraproba metódus minden IOException hibát újrapróbál (when feltétel nélkül), és a cél mappa megtelt lemezen van. Mi történik?
Jóslat ellenőrzése
A program hiába vár (a szünetek összege ~1,2 másodperc, nagyobb korlátnál sokkal több), mert a megtelt lemez nem szűnik meg magától, és a felhasználó késve kapja meg az üzenetet. A when (ZarHiba(ex)) szűrő csak az átmeneti hibára próbál újra.
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 mentés megszakadása elvesztette az adatot
File.WriteAllText(ut, nagyTartalom); // közvetlenül a célfájlbaHiba és javítás
Félbeszakadáskor a fájl csonka vagy üres lehet. Használd az ideiglenes fájl + File.Replace mintát (BiztonsagosFajl.Ment).
2. A catch nem fogja el az írásvédett fájl hibáját
try { File.WriteAllText(ut, s); }
catch (IOException) { /* ... */ }Hiba és javítás
Az UnauthorizedAccessException nem IOException. Adj hozzá külön catch ágat.
3. A fordító jelzi: a korábbi catch már kezeli
catch (IOException) { }
catch (FileNotFoundException) { } // sosem futHiba és javítás
A szűkebb típus (FileNotFoundException) áll az általános után. Cseréld fel a sorrendet: szűkebb elöl.
4. A hibaüzenet angol és érthetetlen
catch (Exception ex) { MessageBox.Show(ex.Message); }Hiba és javítás
A felhasználó nem érti: „The process cannot access the file ...”. Használj érthető szöveget (FajlHiba.Szoveg): mi, miért, mit tegyen. A teljes kivételt naplózd.
5. A program elveszi a felhasználó adatát hibás betöltés után
A hibás fájl betöltése után a program üres adattal mentett. A betöltés hibája után az üres adatot a következő mentés felülírta a fájlt. Hibás betöltés után ne mentsd felül automatikusan a fájlt; ajánld fel a tartalékot (Ok és javítás
.bak), és tiltsd le a mentést, amíg a felhasználó nem dönt.
6. Megmaradnak az ideiglenes .tmp fájlok
A mappában egyre több .tmp fájl gyűlik. A hiba esetén nem törlődött az ideiglenes fájl. A takarítás a Ok és javítás
finally blokkba való (try–finally), és indításkor a régi .tmp fájlok törölhetők.
Mini projekt: biztonságos mentő-labor
Készíts kísérleti programot, amely szimulálja a tipikus fájlhibákat, és bemutatja a helyes kezelésüket:
- Felület:
txtTartalom(a mentendő szöveg),txtUt+ Tallózás... (a cél fájl), Mentés (egyszerű) és Mentés (biztonságos) gombok, Betöltés; szimulátorok:chkZar(A fájl zárolása 3 másodpercig),chkIrasvedett(Írásvédett),chkMegszakad(A mentés megszakad félúton), Főfájl megrongálása;lstNaplo(az események időbélyeggel). - Segédosztályok:
FajlHiba(érthető üzenet),BiztonsagosFajl(Ment,Betolt,Ujraproba) a lecke szerint. - Egyszerű mentés: közvetlen
File.WriteAllText; a megszakad jelölő kivételt dob a kiírás közepén (a fájl csonka marad): ez mutatja a probléma lényegét; a naplóban a fájl mérete előtte és utána. - Biztonságos mentés: ugyanaz a megszakítás után a fájl érintetlen, az ideiglenes fájl törlődik; a napló jelzi (a régi tartalom megmaradt).
- Zárolás: a szimulátor
FileStream(FileShare.None) nyit a fájlra, és egyTimer3 másodperc múlva felszabadítja; a mentés azUjraprobasegítségével eközben többször próbálkozik; a napló a próbák számát mutatja; kevés próbánál (darab = 2) az Újrapróbálod? ablak jelenik meg. - Írásvédettség: a
File.SetAttributes(ut, FileAttributes.ReadOnly); a mentésUnauthorizedAccessExceptionkivételt kap, az üzenet érthető; a Védelem megszüntetése gomb (File.SetAttributes(ut, FileAttributes.Normal)) segít. - Betöltés tartalékból: a Főfájl megrongálása hibás tartalmat ír; a Betöltés a
.bakfájlból olvas, és jelzi (a főfájl sérült, a tartalék másolatot töltöttem be); mentés előtt a felhasználó dönt. - Napló és üzenetek: minden hiba
ex.ToString()alakban ahibak.logfájlba (10.2 lecke); a felhasználó csak az érthető üzenetet látja; az ürescatchtilos. - Teszteld: minden szimulátor külön és együtt, megtelt lemez (kis méretű virtuális meghajtón, ha van), nem létező mappa, nagyon hosszú útvonal, 10 egymás utáni mentés (a
.bakmindig az előző), a .tmp fájlok hiánya a mappában.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A FajlHiba és a BiztonsagosFajl a leckében látható; itt a szimulátorok és a gombok vezérlése áll.
private FileStream zarFolyam;
private System.Windows.Forms.Timer zarIdozito = new System.Windows.Forms.Timer { Interval = 3000 };
private void Form1_Load(object sender, EventArgs e)
{
txtUt.Text = Path.Combine(Path.GetTempPath(), "mento-labor", "adat.txt");
zarIdozito.Tick += (s, ev) => ZarFeloldas();
}
private void Naploz(string uzenet)
{
lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " " + uzenet);
}
private void NaplofajlbaIr(Exception ex)
{
try
{
string mappa = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MentoLabor");
Directory.CreateDirectory(mappa);
File.AppendAllText(Path.Combine(mappa, "hibak.log"), DateTime.Now.ToString("s") + Environment.NewLine + ex + Environment.NewLine + Environment.NewLine);
}
catch (IOException) { } // a naplózás hibája nem akadályozhatja a programot
}
private void ZarKezdet()
{
if (!File.Exists(txtUt.Text)) { return; }
zarFolyam = new FileStream(txtUt.Text, FileMode.Open, FileAccess.Read, FileShare.None); // más nem fér hozzá
zarIdozito.Start();
Naploz("A fájl zárolva (3 másodpercig).");
}
private void ZarFeloldas()
{
zarIdozito.Stop();
if (zarFolyam != null) { zarFolyam.Dispose(); zarFolyam = null; Naploz("A zár megszűnt."); }
}
private void Mentes(bool biztonsagos)
{
string ut = txtUt.Text;
string tartalom = txtTartalom.Text;
try
{
Directory.CreateDirectory(Path.GetDirectoryName(ut));
if (chkIrasvedett.Checked && File.Exists(ut)) { File.SetAttributes(ut, FileAttributes.ReadOnly); }
if (chkZar.Checked) { ZarKezdet(); }
long elotte = File.Exists(ut) ? new FileInfo(ut).Length : -1;
if (biztonsagos)
{
BiztonsagosFajl.Ujraproba(() => BiztonsagosMent(ut, tartalom), 4, 400);
}
else
{
using (StreamWriter iro = new StreamWriter(ut, false, new UTF8Encoding(false)))
{
iro.Write(tartalom.Substring(0, tartalom.Length / 2));
if (chkMegszakad.Checked) { throw new IOException("Szimulált megszakadás"); }
iro.Write(tartalom.Substring(tartalom.Length / 2));
}
}
Naploz((biztonsagos ? "Biztonságos" : "Egyszerű") + " mentés kész. A fájl mérete: " + elotte + " → " + new FileInfo(ut).Length + " bájt.");
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
bool ujra;
NaplofajlbaIr(ex);
Naploz(FajlHiba.Szoveg(ex, "A mentés", ut, out ujra));
Naploz("A fájl mérete a hiba után: " + (File.Exists(ut) ? new FileInfo(ut).Length + " bájt" : "a fájl nincs"));
}
finally
{
if (chkIrasvedett.Checked && File.Exists(ut)) { File.SetAttributes(ut, FileAttributes.Normal); }
}
}
// a megszakad jelölő a biztonságos mentésnél is dobhat kivételt az ideiglenes fájl írása közben
private void BiztonsagosMent(string ut, string tartalom)
{
string ideiglenes = ut + "." + Guid.NewGuid().ToString("N") + ".tmp";
try
{
using (FileStream fs = new FileStream(ideiglenes, FileMode.CreateNew, FileAccess.Write, FileShare.None))
using (StreamWriter iro = new StreamWriter(fs, new UTF8Encoding(false)))
{
iro.Write(tartalom.Substring(0, tartalom.Length / 2));
if (chkMegszakad.Checked) { throw new IOException("Szimulált megszakadás"); }
iro.Write(tartalom.Substring(tartalom.Length / 2));
iro.Flush();
fs.Flush(true);
}
if (File.Exists(ut)) { File.Replace(ideiglenes, ut, ut + ".bak"); } else { File.Move(ideiglenes, ut); }
}
finally
{
try { if (File.Exists(ideiglenes)) { File.Delete(ideiglenes); } } catch (IOException) { }
}
}
private void btnEgyszeru_Click(object sender, EventArgs e) { Mentes(false); }
private void btnBiztonsagos_Click(object sender, EventArgs e) { Mentes(true); }
private void btnMegrongal_Click(object sender, EventArgs e)
{
File.WriteAllText(txtUt.Text, "{ sérült tartalom");
Naploz("A főfájl megrongálva.");
}
private void btnBetolt_Click(object sender, EventArgs e)
{
bool tartalekbol;
// érvényesnek tekintjük, ha nem kezdődik „{ sérült” jellel (a labor egyszerű ellenőrzése)
string szoveg = BiztonsagosFajl.Betolt(txtUt.Text, s => !s.StartsWith("{ sérült"), out tartalekbol);
if (szoveg == null) { Naploz("Nincs használható fájl."); return; }
txtTartalom.Text = szoveg;
Naploz(tartalekbol ? "A főfájl sérült: a tartalék (.bak) másolatot töltöttem be." : "Betöltve a főfájlból.");
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
ZarFeloldas();
}Mit tegyél ide: a kész mentő-labor a szimulátorok kipipálva és a napló üzeneteivel.Javasolt fájlnév: winforms-10-9-kesz.webp · Alt szöveg: A kész biztonságos mentő-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. Időbélyeges biztonsági másolatok
Mentésenként külön A mentés előtt a régi fájl másolása .bak másolat készüljön időbélyeggel, de legfeljebb az utolsó 5 maradjon. Hogyan?Egy lehetséges megoldás
nev.2026-10-28_103000.bak névre; a mappa *.bak fájljai rendezve (név szerint, mivel az időbélyeg rendezhető), és az ötnél régebbiek törölve.string bak = ut + "." + DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + ".bak";File.Copy(ut, bak, false);foreach (string regi in Directory.GetFiles(Path.GetDirectoryName(ut), Path.GetFileName(ut) + ".*.bak").OrderByDescending(f => f).Skip(5)){ File.Delete(regi);}
2. Szabad hely ellenőrzése
Mentés előtt a program ellenőrzi, hogy van-e elég szabad hely a célmeghajtón. Mit használsz? A Egy lehetséges megoldás
DriveInfo: new DriveInfo(Path.GetPathRoot(ut)).AvailableFreeSpace összevetve a várt mérettel (és egy ráhagyással); hálózati elérési útnál (UNC) a DriveInfo nem használható. Ez előzetes jelzés, a mentést a try–catch védi.
3. Indításkor a félbemaradt ideiglenes fájlok
Az előző futás összeomlott, és .tmp fájlok maradtak. Mit teszel induláskor? Az adatmappában a Egy lehetséges megoldás
*.tmp fájlok törlése (hiba esetén átugrással); a félig megírt ideiglenes fájl sosem használható adatforrás, mert a csere nem történt meg.
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. Melyik kivétel?
Melyik kivételt kapod: (a) nem létező mappába mentés; (b) írásvédett fájl írása; (c) másik program által zárolt fájl olvasása? (a) Megoldás megnyitása
DirectoryNotFoundException; (b) UnauthorizedAccessException; (c) IOException (32-es hibakód).
2. Jó hibaüzenet
Írd át érthetőbbre: Hiba: IOException. A mentés nem sikerült, mert a fájlt másik program használja. Zárd be, majd próbáld újra.: mi, miért, mit tegyen.Megoldás megnyitása
Közepes szint
3. Mikor ne próbálkozz újra?
Sorolj fel három hibát, amelynél az újrapróbálkozás nem segít. Jogosultság hiánya, megtelt lemez, érvénytelen útvonal (és a hiányzó mappa): ezek nem szűnnek meg magukról.Megoldás megnyitása
4. Az utolsó próba hibája
Az A hívó értesül a hibáról (nem nyelődik el), és megjelenítheti az üzenetet. Az Ujraproba az utolsó próbánál a when feltétel miatt továbbadja a kivételt. Miért jó ez?Megoldás megnyitása
i < darab feltétel az utolsó próbát kiveszi a fogásból.
Kihívás szint
5. Atomi mentés hálózati meghajtón
A File.Replace hálózati megosztáson hibázik. Mi a tartalék terv?Megoldás megnyitása
File.Copy(cel, bak, true) a régiről, majd az ideiglenes fájl átnevezése (File.Delete(cel) és File.Move); a két lépés között rövid időre nincs célfájl, ezért a hiba esetén a .bak a visszaút. Ez gyengébb, mint az atomi csere: a dokumentációban jelezd.
6. Párhuzamos mentés két példányból
A programot kétszer indítják, mindkettő ugyanabba a fájlba ment. Mi a veszély, és mi a védekezés? Az utolsó mentés felülírja az előzőt (elvész a másik módosítása). Védekezés: egy példány engedélyezése (Megoldás megnyitása
Mutex), vagy a mentés előtti LastWriteTime összehasonlítás a betöltéskor tárolt értékkel és megerősítés.
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Az első egyező ág fut; az általános Nem: nem leszármazottja. Mi nem sikerült, miért, és mit tehet a felhasználó. Ideiglenes fájlba írás, Hogy a csere azonos meghajtón, egyetlen megszakíthatatlan lépésben történjen. Csak átmeneti hibánál (zárolt fájl), korlátozott számú próbával; jogosultság és megtelt lemez esetén nem. Elrejti a hibát: a felhasználó azt hiszi, hogy a mentés sikerült, és elveszítheti az adatát.1. Miért kell a szűkebb catch elöl?
IOException elöl elnyelné a szűkebbeket.2. Elkapja-e az IOException az UnauthorizedAccessException-t?
3. Mit tartalmaz a jó hibaüzenet?
4. Mi a biztonságos mentés lényege?
Flush(true), File.Replace biztonsági másolattal: a régi fájl hiba esetén ép marad.5. Miért az ideiglenes fájl ugyanabban a mappában?
6. Mikor próbálkozz újra?
7. Miért veszélyes az üres catch?
Mit tudsz már?
- Ismered a fájlkivételek családját és okaikat, és helyes sorrendben fogod el őket.
- Érthető, használható hibaüzenetet adsz, és naplózod a teljes hibát.
- Biztonságosan mentesz (ideiglenes fájl,
File.Replace, biztonsági másolat). - Betöltéskor a sérült fájl helyett a tartalékot ajánlod fel.
- Átmeneti hibánál szűrt, korlátozott újrapróbálkozást használsz.
Következő lecke: mini projekt – jegyzetek és beállítások tárolása
A 10. fejezet záró projektje: jegyzetkezelő JSON-tárolással, beállításokkal, biztonságos mentéssel, importtal és exporttal.



