A .NET keretrendszerben a hibák kezelésének szabványos módja a kivételkezelési mechanizmuson alapszik. Számos .NET nyelv támogatja ezt az eljárást, beleértve az F#-ot is.
Az F#-ban a kivételeknek két csoportját különböztethetjük meg: beszélhetünk .NET-kivételtípusokról, illetve F#-kivételtípusokról. Ebben a részben az F#-kivételtípusokról lesz szó. A kivételek definiálásának általános szintaxisa alább látható.
Az elobbi definícióban az exception-type az új F#-kivétel neve, az argument-type pedig azt a típust reprezentálja, amelynek megfelelo típusú kivételt a késobbiek folyamán szeretnénk kiváltani. Egy konkrét példa kivétel definiálására:
Az elobbi típusú kivételek kiváltására használható a raise függvény:
Az F#-kivételek közvetlenül felhasználhatók egy try...with kifejezésben, ahogy ezt az alábbi példakód is mutatja:
Az F#-ban az exception kulcsszóval definiált új kivételtípusok az Exception osztály leszármazottai.
A try...with kifejezések általános szintaxisa a következoképpen néz ki:
Az elobbi kifejezés használható F#-ban a kivételek kezelésére, amely egyébként hasonló a C#-ban szereplo try...catch utasításhoz. A fenti kódrészletben az expression1 az a kifejezés, ami kivételt válthat ki. Maga a teljes try...with kifejezés egy meghatározott értékkel tér vissza. Amennyiben a védett kódrészletben nem lép fel kivétel, úgy a teljes kifejezés értékét az expression1 kifejezés szolgáltatja. Egy kivétel kiváltódása esetén azonban a fellépo kivétel a with után megadott mintákkal kerül összehasonlításra, és az elso egyezés esetén a mintának megfelelo kifejezés hajtódik végre (ez felel meg a kivételkezelo résznek), így tehát az egész try...with kifejezés értékét a kivételkezelo kifejezés adja meg. Ha a with utáni minták közül egyikre sem sikeres a mintaillesztés, akkor a végrehajtási veremben szereplo hívási lánc szerint terjed tovább felfelé a kivétel egészen addig, amíg a megfelelo kivételkezelo megtalálásra nem kerül. Fontos kiemelni, hogy a kivételkezelokhöz tartozó kifejezések által visszaadott értékek típusának meg kell egyeznie a try-blokkban szereplo kifejezés visszatérési értékének típusával. Gyakran elofordul, hogy egy hiba fellépése során nem tudunk értelmes értéket visszaadni egy kivételkezelo kifejezésben. Ilyenkor általában azt érdemes csinálni, hogy a try-blokk kifejezése által visszaadott érték típusát Option típusúnak választjuk. Az alábbi kódrészlet erre mutat egy példát:
Mint már korábban említésre került, a kivételek lehetnek .NET-kivételek, illetve F#-kivételek. A kivételkezelokhöz tartozó minták megírásához számos lehetoség kínálkozik, ezeket foglalja össze a következo táblázat.
Minta | Leírás |
---|---|
:? kivétel-típus | Adott .NET-kivétel típusra illeszkedik. |
:? kivétel-típus as azonosító | Adott .NET-kivétel típusra illeszkedik, de a kivétel névhez van kötve. |
kivétel-név(argumentumok) | Adott F#-kivétel típusra illeszkedik, és az argumentumok is kötésre kerülnek. |
azonosító | Akármilyen kivételre illeszkedik, maga a kivételobjektum ehhez a névhez lesz hozzákötve. A következovel ekvivalens: :? System.Exception as azonosító |
azonosító when feltétel | Akármilyen kivételre illeszkedik, ha a feltétel igaz. |
A következő kódrészlet a különböző kivételkezelési mintákra mutat példát:
Fontos megjegyezni, hogy a try...with konstrukció egy önálló kifejezés, így különbözik a try...finally kifejezéstől. Amennyiben mind a with, mind a finally-blokkra szükségünk van, úgy a két kifejezést össze kell illesztenünk.
A try...finally kifejezés segítségével olyan kódrészletet írhatunk, amely mindenképpen végrehajtódik, így tehát akkor is, amikor egy blokkban kivétel lépett fel. Az általános szintaxis a következő:
Ez a kifejezés tehát arra használható, hogy az expression2 kifejezésnek megfelelő kód mindenképpen végrehajtódjon függetlenül attól, hogy az expression1 kifejezés futása során keletkezett-e kivétel vagy sem. Az expression2 kifejezés típusa nem vesz részt a teljes kifejezés értékének meghatározásában, vagyis ha nem lép fel kivétel, akkor az expression1 utolsó értéke határozza meg a típust. Ha mégis fellép egy kivétel, akkor nem adódik vissza semmilyen érték, hanem átkerül a vezérlés a hívási lánc szerinti első egyező kivételkezelőhöz. Amennyiben nincs megfelelő kivételkezelő, úgy a program terminál. Mielőtt azonban a megfelelő kivételkezelő lefutna vagy a program terminálna, végrehajtásra kerül a finally-blokk.
A következő kódrészlet a try...finally kifejezés használatára mutat egy példát:
A konzolra az alábbi szöveg íródik ki:
Látható, hogy a stream még azelőtt lezárásra került, hogy a (külső) kivétel lekezelődött volna. Így a test.txt nevű fájl a „test1” szöveget tartalmazza, ez pedig azt jelzi, hogy a buffer még az előtt ürítődött ki (és íródott a tartalma a lemezre), hogy a vezérlés átadódott volna a külső kivételkezelő résznek.
A következőkben arra láthatunk példát, hogy hogyan kell a try...with és a try...finally szerkezeteket egyszerre használni (azaz, amikor a with és a finally-blokkokra egyszerre van szükségünk):
A kimenet alább látható:
A raise függvény valamilyen hiba vagy kivétel fellépése esetén használható, és a hibával kapcsolatos információt a kivételobjektum tartalmazza. Az általános szintaxis a következő:
Amikor a raise függvényt meghívjuk, akkor egy kivételobjektum generálódik, és egyúttal megkezdődik a végrehajtási verem visszagörgetésének folyamata. Ezt a folyamatot a .NET futtatókörnyzete, a CLR (Common Language Runtime) felügyeli, ezért a viselkedés is ugyanaz lesz, mint más .NET-es nyelvek esetében. A verem visszagörgetése tulajdonképpen egy olyan kivételkezelő keresését jelenti, amely illeszkedik a fellépő kivételre. Így tehát a keresés az aktuális try...with kifejezésben kezdőik (ha van ilyen). Ekkor a with-blokk összes mintája sorban megvizsgálásra kerül, és sikeres illeszkedés esetén a mintához tartozó kivételkezelő kifejezés hajtódik végre, különben folytatódik tovább a visszagörgetés a hívási lánc szerint addig, amíg meg nem lesz a megfelelő kivételkezelő. Eközben az összes olyan finally-blokk is végrehajtódik, amely a visszagörgetés során előfordult.
A raise függvény ekvivalens a C#-ból ismert throw utasítással. Amennyiben ugyanazt a kivételt még egyszer ki akarjuk váltani egy kivételkezelőben, akkor ehhez a reraise függvényt használhatjuk.
A raise függvénnyel .NET-es kivételek is kiválthatók, ezt mutatja az alábbi példa:
A failwith függvény egy F#-kivételt vált ki, használata a következő formában történik:
Az error-message-string vagy egy string literál vagy egy string típusú érték, és ez képezi majd a keletkező kivételobjektum Message property-jét.
A failwith függvénnyel kiváltott kivétel típusa Microsoft.FSharp.Core.FailureException, amelyre az F#-kódban Failure névvel hivatkozhatunk. A következő kódrészlet bemutatja, hogy hogyan kell a failwith függvénnyel kivételt kiváltani:
Az invalidArg függvény valamilyen hibás argumentummal kapcsolatos kivételt vált ki, használata a következőképpen történik:
A parameter-name egy string, amely a hibás paraméter nevére vonatkozik. Az error-message-string pedig szintén egy string, amely a keletkező kivételobjektum Message property-jét képezi.
Az invalidArg függvénnyel kiváltott kivétel típusa System.ArgumentException. Az alábbi kódrészletben láthatjuk, hogy hogyan kell az invalidArg függvénnyel kivételt kiváltani:
Az eredmény a következő (amit a stack trace követ):