Grafikus C# – Windows Forms tanfolyam › 10.9 lecke

Windows Forms · 10.9 lecke · megbízható mentés

Mentési hibák, fájlzárak és kivételkezelés

A 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.

  • kb. 110 perc
  • haladó szint
  • megbízhatóság
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • ismered a fájlkivételek családját, és a catch blokkok 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 (when kivé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.

10.8 Application Settings →

A fájlkivételek családja és okaik

KivételTipikus okÚjrapróbálható?
FileNotFoundExceptionA fájl nem létezik (vagy időközben törölték).Nem
DirectoryNotFoundExceptionA mappa nem létezik (törölt mappa, lecsatolt meghajtó).Nem (a mappát létre lehet hozni)
UnauthorizedAccessExceptionNincs jogosultság; írásvédett fájl; a cél egy mappa; a rendszermappa.Nem
PathTooLongExceptionAz ú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
DriveNotFoundExceptionA 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)
Form1.csC#
// 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
}
Szűkebb elölA 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).
Az IOException okaiAz 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).
UnauthorizedAccessExceptionNem 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.
Mit ne fogj elAz általános 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:

FajlHiba.csC#
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;
}
}
Három kérdésJó hibaüzenet: mi nem sikerült (Mentés), miért (a fájlt másik program használja), mit tegyen (zárd be, próbáld újra). A „Hiba történt” és az „IOException” egyiket sem mondja meg.
Egy hely, egységes üzenetA központi 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.
NaplózásA felhasználónak az érthető üzenet kell, a fejlesztőnek a teljes kivétel (ex.ToString() a hívási lánccal). Írd naplófájlba (10.2 lecke), hogy a hiba később kivizsgálható legyen.
Ne nyeld elAz üres 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:

  1. az új tartalmat ideiglenes fájlba írod, ugyanabban a mappában (azonos meghajtó);
  2. Flush(true): az adat ténylegesen a lemezre kerül (nem csak a rendszer gyorsítótárába);
  3. a File.Replace(ideiglenes, cél, biztonsagiMasolat) egy lépésben cseréli az újat a régire, és a régit .bak néven megtartja;
  4. ha bármi hibázik, a régi fájl érintetlen, és az ideiglenes fájl törlődik.
BiztonsagosFajl.cs (using System.IO; using System.Text;)C#
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ó
}
}
Miért ugyanabban a mappában?A 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(true)A 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.ReplaceA cél fájlt az ideiglenessel helyettesíti, és a régit a harmadik paraméterben megadott néven megőrzi. Hálózati megosztásokon és némely fájlrendszeren korlátozottan működik; ilyenkor tartalék: File.Copy(cel, bak, true), majd File.Copy(ideiglenes, cel, true).
Biztonsági másolat és tartalékA .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.

📷 Képernyőkép helye

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:

BiztonsagosFajl.cs (using System.Threading;)C#
// 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
}
}
Kivétel-szűrő (when)A 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.
Növekvő szünetA szünet minden körben nő (200, 400 ms): a zár megszűnésére esélyt ad, de nem tart sokáig. A 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.
Mikor nem szabad újrapróbálniJogosultság, megtelt lemez, érvénytelen útvonal: az újrapróbálás nem segít, csak késlelteti az üzenetet. Az utolsó próba hibája mindig tovább kerül a hívóhoz.
Mégis a felhasználóra bízvaHa az automatikus próbák kimerülnek, a 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ájlba
Hiba é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 fut
Hiba é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.

Ok és javítás

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 (.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.

Ok és javítás

A hiba esetén nem törlődött az ideiglenes fájl. A takarítás a 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:

  1. 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).
  2. Segédosztályok: FajlHiba (érthető üzenet), BiztonsagosFajl (Ment, Betolt, Ujraproba) a lecke szerint.
  3. 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.
  4. 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).
  5. Zárolás: a szimulátor FileStream (FileShare.None) nyit a fájlra, és egy Timer 3 másodperc múlva felszabadítja; a mentés az Ujraproba segí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.
  6. Írásvédettség: a File.SetAttributes(ut, FileAttributes.ReadOnly); a mentés UnauthorizedAccessException kivételt kap, az üzenet érthető; a Védelem megszüntetése gomb (File.SetAttributes(ut, FileAttributes.Normal)) segít.
  7. Betöltés tartalékból: a Főfájl megrongálása hibás tartalmat ír; a Betöltés a .bak fá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.
  8. Napló és üzenetek: minden hiba ex.ToString() alakban a hibak.log fájlba (10.2 lecke); a felhasználó csak az érthető üzenetet látja; az üres catch tilos.
  9. 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 .bak mindig 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.

Form1.cs (using System.IO; using System.Text; using System.Threading;)C#
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();
}
📷 Képernyőkép helye

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 .bak másolat készüljön időbélyeggel, de legfeljebb az utolsó 5 maradjon. Hogyan?

Egy lehetséges megoldás

A mentés előtt a régi fájl másolása 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.

Form1.csC#
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?

Egy lehetséges megoldás

A 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?

Egy lehetséges megoldás

Az adatmappában a *.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?

Megoldás megnyitása

(a) DirectoryNotFoundException; (b) UnauthorizedAccessException; (c) IOException (32-es hibakód).

2. Jó hibaüzenet

Írd át érthetőbbre: Hiba: IOException.

Megoldás megnyitása

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.

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.

Megoldás megnyitása

Jogosultság hiánya, megtelt lemez, érvénytelen útvonal (és a hiányzó mappa): ezek nem szűnnek meg magukról.

4. Az utolsó próba hibája

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

A hívó értesül a hibáról (nem nyelődik el), és megjelenítheti az üzenetet. Az 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?

Megoldás megnyitása

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 (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.

1. Miért kell a szűkebb catch elöl?

Az első egyező ág fut; az általános IOException elöl elnyelné a szűkebbeket.

2. Elkapja-e az IOException az UnauthorizedAccessException-t?

Nem: nem leszármazottja.

3. Mit tartalmaz a jó hibaüzenet?

Mi nem sikerült, miért, és mit tehet a felhasználó.

4. Mi a biztonságos mentés lényege?

Ideiglenes fájlba írás, 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?

Hogy a csere azonos meghajtón, egyetlen megszakíthatatlan lépésben történjen.

6. Mikor próbálkozz újra?

Csak átmeneti hibánál (zárolt fájl), korlátozott számú próbával; jogosultság és megtelt lemez esetén nem.

7. Miért veszélyes az üres catch?

Elrejti a hibát: a felhasználó azt hiszi, hogy a mentés sikerült, és elveszítheti az adatát.

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.