10. Kivételkezelés és Fájlkezelés
A programozás során elkerülhetetlen, hogy különböző hibák lépjenek fel. A hagyományos hibakezelés során a funkcionális és a hibakezelő kódrészek gyakran összekeverednek, a hibákat pedig speciális visszatérési értékekkel jelzik, ami jelentősen rontja a kód olvashatóságát. Ezt a problémát hivatott kiküszöbölni az objektumorientált kivételkezelés.
1. A kivételek és a kivételkezelés alapjai
A kivétel (exception) egy olyan esemény a programvégrehajtás során, amely megzavarja a normál programműködést.
-
Az objektumorientált programozásban a kivétel egy objektum, ami a hiba bekövetkezésekor jön létre.
-
Ez a kivételobjektum fontos információkat tartalmaz: megadja a hiba típusát, és rögzíti a program állapotát a kivétel bekövetkezésekor.
-
Kivétel keletkezhet automatikusan egy utasítás végrehajtása közben (például I/O hiba, tömbindex túllépés, sikertelen típuskonverzió), de a programkódban kiadott
throwutasítás hatására is kiváltható. -
Amikor egy kivétel létrejön, a kivételt kiváltó utasítással az adott blokk végrehajtása azonnal befejeződik.
2. A try-catch-finally szerkezet
A kivételkezelő utasítás a C# és a Java nyelvben egyaránt a try-catch-finally blokk.
-
A try blokk: Ebbe a blokkba kell írni a normál működéshez tartozó olyan kódrészeket, amelyek kivételt dobhatnak. A
tryblokkok egymásba is ágyazhatók. -
A catch blokk: Ha a
tryblokkban kivétel dobódik, a rendszer megkeresi a megfelelőcatchblokkot, ami képes azt lekezelni. Acatchparamétere egy kivételobjektum referencia. Kivétel bekövetkezésekor az első olyancatchblokk kapja el a kivételt, amelyik képes a kezelésére, így többcatchblokk esetén a sorrendjük rendkívül fontos. Mindentryblokkhoz kötelező legalább egycatchblokkot írni. -
A finally blokk: Ennek a blokknak a feladata az erőforrások felszabadítása. Úgy kell megírni, hogy mindig lefuthasson, függetlenül attól, mi történt a
tryblokkban. Ugyanakkor a jegyzet felhívja a figyelmet egy speciális esetre: ha atryblokkreturnutasítást tartalmaz és az végrehajtásra kerül, akkor afinallyblokk nem fut le.
3. A C# kivételosztályainak hierarchiája
Minden kivétel őse a System.Exception osztály. Ennek az osztálynak nagyon fontos tulajdonságai vannak:
-
StackTrace: Ennek a tulajdonságnak a segítségével állapítható meg pontosan, hogy a programkódban hol keletkezett a kivétel.
-
Message: Ez a tulajdonság tartalmazza a kivétel szöveges leírását.
-
InnerException: Akkor kap értéket, ha több kivétel is történik; a legutoljára keletkezett kivétel ezen keresztül követhető vissza az eredeti (kiváltó) kivételig.
A C# kivételek két fő csoportra oszthatók:
-
SystemException: A futtatókörnyezet (CLR) által kiváltott kivételek (például
DivideByZeroException,IndexOutOfRangeException,NullReferenceException). -
ApplicationException: Kifejezetten a program által kiváltott kivételek.
Amikor egy kivételt a catch blokkban elkapunk, dönthetünk úgy, hogy továbbdobjuk. C#-ban a legjobb megoldás az egyszerű throw; utasítás használata, vagy egy új kivétel dobása az eredeti kivétel beágyazásával. A throw ex; használata rossz megoldás, mert teljesen törli a kivétel stack-et (a visszakövethetőséget).
4. Fájlkezelés (I/O műveletek) a C#-ban
A C# adatfolyamokat (stream) használ az I/O (Input/Output) műveletek elvégzéséhez. Az ehhez szükséges osztályok a System.IO névtérben találhatók.
A karakteres fájlkezelés lépései a következők:
-
Fájl adatfolyam létrehozása: A
FileStreamosztály segítségével. A konstruktorának első paramétere a fájl neve, a második aFileMode(példáulCreate,Open,Append), a harmadik aFileAccess(példáulRead,Write), a negyedik pedig aFileShare(ami a más processzekkel való megosztást szabályozza). -
Író/olvasó adatfolyam létrehozása: Szöveges fájlok írásához a
StreamWriter, olvasásához aStreamReaderosztályokat használjuk, átadva nekik a létrehozott fájl adatfolyamot. Bináris adatokhoz aBinaryReaderésBinaryWriterosztályok használatosak. Bináris olvasásnál kiemelten figyelni kell arra, hogy a kiolvasás mindig a típusnak megfelelő metódussal történjen (pl.ReadInt32()), különben kivétel keletkezik. -
Lezárás: A megnyitott adatfolyamokat a műveletek végeztével fordított sorrendben le kell zárni a
.Close()metódussal. Ennek egy modernebb és biztonságosabb alternatívája ausingblokk, amely automatikusan lezárja a paraméterében megnyitott adatfolyamot.
A statikus File osztály metódusaival is végezhetünk műveleteket; például a File.CreateText() vagy File.OpenText() hívásokkal, amelyeket szintén egy using blokkba érdemes tenni.
Gyakorló feladat
Készíts egy programot, amely bekér egy fájlnevet és egy szöveget a felhasználótól, majd megpróbálja elmenteni a szöveget a megadott fájlba! Használj megfelelő try-catch blokkot a lehetséges IO hibák (például ha a fájl neve érvénytelen karaktereket tartalmaz) lekezelésére! A fájlba íráshoz használd a StreamWriter osztályt egy using blokkon belül.
A feladat megoldása
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
Console.Write("Kérem a fájl nevét kiterjesztéssel (pl. teszt.txt): ");
string fajlNev = Console.ReadLine();
Console.Write("Kérem a szöveget, amit menteni szeretnél: ");
string szoveg = Console.ReadLine();
try
{
// A using blokk gondoskodik a StreamWriter lezárásáról
using (StreamWriter sw = new StreamWriter(fajlNev))
{
sw.WriteLine(szoveg);
}
Console.WriteLine("A mentés sikeresen megtörtént!");
}
catch (Exception ex)
{
// Kivételkezelés, ha bármilyen hiba történik az írás során
Console.WriteLine("Hiba történt a fájl írása közben!");
Console.WriteLine($"A hiba részletei (Message): {ex.Message}");
}
Console.ReadKey();
}
}