A les lliçons anteriors has après a prendre decisions, a repetir accions i a classificar
valors, però sempre assumint que les dades arriben correctes: un número que realment és un
número, un índex que existeix dins de l'array, un llibre que sí que és al catàleg. En un
programa real, això no està garantit: un usuari pot escriure text on s'espera un número, es pot
intentar deixar un llibre que no existeix, o dividir per un valor que resulta ser zero. Quan
passa una cosa així, C# no continua com si res: llança una excepció, un objecte especial
que interromp el flux normal del programa per assenyalar que alguna cosa ha anat malament.
Aquesta lliçó, l'última del Mòdul 2, t'ensenya a anticipar aquestes situacions i a
gestionar-les de forma controlada amb try/catch/finally, en lloc de deixar que el
programa s'aturi bruscament.
Contingut
- Què és una excepció?
try/catch: capturar errorsfinally: codi que sempre s'executa- Llançar excepcions pròpies amb
throw - Tipus d'excepció habituals
- Capturar múltiples tipus d'excepció
- Bones pràctiques amb excepcions
- Aplicació completa: deixar un llibre de forma segura
- Què és una excepció?
Una excepció és un objecte (d'una classe que deriva, directament o indirectament, de
System.Exception) que representa un error ocorregut durant l'execució del programa. Si no es
fa res per gestionar-la, l'excepció es propaga cap amunt fins que, si ningú la captura, el
programa acaba de forma abrupta i mostra a la consola un missatge d'error juntament amb la
pila de trucades (stack trace): la seqüència de mètodes que s'estaven executant quan es
va produir la fallada.
int[] copiesDisponibles = { 3, 0, 5 };
Console.WriteLine(copiesDisponibles[10]); // IndexOutOfRangeException: el programa atura aqui
Console.WriteLine("Aquesta linia no arriba mai a executar-se");En executar aquest codi, la consola mostraria alguna cosa semblant a:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.Main(String[] args)
L'objectiu d'aquesta lliçó és evitar que errors previsibles com aquest aturin BiblioTechConsola per complet, i en el seu lloc reaccionar-hi de forma controlada.
try/catch: capturar errors
try/catch: capturar errorsEl bloc try embolcalla el codi que podria fallar; el bloc catch defineix què fer si
efectivament falla.
int[] copiesDisponibles = { 3, 0, 5 };
try
{
Console.WriteLine(copiesDisponibles[10]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Error: aquesta posicio no existeix en aquest array.");
}
Console.WriteLine("El programa continua executant-se amb normalitat.");A diferència de l'exemple anterior, aquí el programa no s'atura: l'excepció es captura, es
mostra un missatge comprensible, i l'execució continua a la línia següent al bloc
try/catch.
També es pot accedir a l'objecte de l'excepció per consultar informació addicional, com la
seva propietat Message:
try
{
string entradaUsuari = "no-es-un-numero";
int pagines = int.Parse(entradaUsuari);
Console.WriteLine($"Pagines: {pagines}");
}
catch (FormatException ex)
{
Console.WriteLine($"Error al convertir el text a numero: {ex.Message}");
}
finally: codi que sempre s'executa
finally: codi que sempre s'executaEl bloc finally, opcional, conté codi que s'executa sempre, tant si el bloc try acaba
amb èxit com si es llança i captura una excepció. És el lloc habitual per alliberar recursos
(tancar un fitxer, una connexió de xarxa o de base de dades) que s'han de tancar passi el que
passi.
try
{
Console.WriteLine("Obrint el registre de prestecs...");
int pagines = int.Parse("120");
Console.WriteLine($"Registre processat: {pagines} pagines");
}
catch (FormatException ex)
{
Console.WriteLine($"Error de format: {ex.Message}");
}
finally
{
Console.WriteLine("Tancant el registre de prestecs.");
}En aquest exemple, "Tancant el registre de prestecs." es mostra sempre: tant si la conversió
de "120" té èxit, com si fallés i el catch capturés l'error. Veuràs finally amb molt més
protagonisme al Mòdul 5, en treballar amb fitxers i bases de dades, on tancar correctament els
recursos oberts és crític.
- Llançar excepcions pròpies amb
throw
throwA més de capturar excepcions que llança el mateix entorn d'execució (.NET), el teu codi pot
llançar les seves pròpies excepcions amb throw, per assenyalar una situació anòmala segons
les regles de negoci de BiblioTech.
void RegistrarPrestec(bool llibreDisponible)
{
if (!llibreDisponible)
{
throw new InvalidOperationException("No es pot deixar un llibre que no esta disponible.");
}
Console.WriteLine("Prestec registrat correctament.");
}
try
{
RegistrarPrestec(llibreDisponible: false);
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Operacio no valida: {ex.Message}");
}throw new TipusExcepcio("missatge descriptiu") crea una nova instància de l'excepció
indicada i la llança immediatament, interrompent l'execució normal del mètode fins que algun
bloc catch (en aquest mètode o en qui el crida) la capturi.
- Tipus d'excepció habituals
.NET ofereix moltes classes d'excepció ja preparades per a situacions habituals. Fer servir el
tipus més específic possible (en lloc del genèric Exception) ajuda a qui llegeix el codi —i
a qui el captura— a entendre exactament què ha fallat.
| Excepció | Quan es produeix (o es llança expressament) |
|---|---|
Exception |
Classe base de la qual deriven totes les altres; representa "un error genèric" |
ArgumentException |
S'ha passat un argument no vàlid a un mètode |
ArgumentNullException |
S'ha passat null on s'esperava un valor (deriva d'ArgumentException) |
InvalidOperationException |
S'intenta una operació que no és vàlida en l'estat actual de l'objecte/programa |
FormatException |
Una cadena de text no té el format esperat per convertir-la (per exemple, int.Parse sobre text no numèric) |
IndexOutOfRangeException |
S'accedeix a una posició d'un array fora dels seus límits |
NullReferenceException |
S'intenta fer servir un membre (mètode, propietat) d'una variable que val null |
DivideByZeroException |
Es realitza una divisió entera entre zero |
void ValidarNombrePagines(int pagines)
{
if (pagines <= 0)
{
throw new ArgumentException("El nombre de pagines ha de ser mes gran que zero.", nameof(pagines));
}
}
try
{
ValidarNombrePagines(-50);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Argument invalid: {ex.Message}");
}
- Capturar múltiples tipus d'excepció
Un mateix bloc try pot tenir diversos blocs catch, cadascun per a un tipus diferent
d'excepció. C# avalua els blocs catch de dalt a baix i executa el primer el tipus del
qual coincideixi (o sigui una classe base) amb l'excepció llançada, així que els tipus més
específics han d'anar sempre abans que els més genèrics.
string[] titolsLlibres = { "Cent anys de solitud", "Rayuela", "Ficcions" };
try
{
Console.Write("Introdueix la posicio del llibre que vols consultar: ");
string? entrada = Console.ReadLine();
int posicio = int.Parse(entrada!);
Console.WriteLine(titolsLlibres[posicio]);
}
catch (FormatException)
{
Console.WriteLine("Cal introduir un numero enter valid.");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("No hi ha cap llibre en aquesta posicio del cataleg.");
}
catch (Exception ex)
{
Console.WriteLine($"Hi ha hagut un error inesperat: {ex.Message}");
}Si FormatException i IndexOutOfRangeException estiguessin després de catch (Exception ex), mai s'executarien: el compilador de C# directament marca aquest ordre com un error,
perquè un bloc catch (Exception ex) previ ja capturaria qualsevol excepció, deixant la resta
de blocs inabastables.
També és possible capturar diversos tipus amb un mateix bloc, fent servir l'operador when
per afegir una condició addicional, o simplement definint el bloc per a la classe base comuna
que interessi; per a la majoria de casos quotidians, però, un catch per tipus (com a
l'exemple anterior) és més clar.
- Bones pràctiques amb excepcions
| Bona pràctica | Per què importa |
|---|---|
| Capturar el tipus més específic possible | Facilita donar un missatge precís i no amaga errors d'un altre tipus |
| No fer servir excepcions per al flux normal del programa | Són costoses en rendiment i dificulten seguir la lògica del programa |
No capturar Exception genèrica "perquè sí" |
Pot amagar errors de programació reals que s'haurien de solucionar |
Incloure missatges descriptius a throw new ...(...) |
Ajuda a diagnosticar el problema sense haver d'endevinar la causa |
Fer servir finally (o recursos que es tanquen sols) per alliberar recursos |
Evita fuites de memòria, fitxers bloquejats o connexions obertes |
Sobre el primer punt de la taula —no fer servir excepcions per al flux normal—: si una
situació és previsible i freqüent (per exemple, comprovar si un text es pot convertir a
número), sol ser millor fer servir eines com int.TryParse (que ja vas veure a la lliçó de
variables del Mòdul 1) en lloc d'embolcallar int.Parse en un try/catch i esperar que
falli. Les excepcions estan pensades per a situacions excepcionals, no per a validacions
rutinàries:
// Preferible per a validacions rutinaries: sense excepcions pel mig
string entrada = "abc";
if (int.TryParse(entrada, out int pagines))
{
Console.WriteLine($"Nombre de pagines: {pagines}");
}
else
{
Console.WriteLine("Entrada no valida.");
}
// Reserva try/catch per al que sigui veritablement inesperat o fora del teu control,
// com accedir a un array amb una posicio que en teoria mai hauria de passar.
- Aplicació completa: deixar un llibre de forma segura
Tanquem la lliçó —i el mòdul— amb un exemple que combina diverses de les idees vistes: buscar un llibre pel títol, validar l'entrada de l'usuari, i llançar i capturar excepcions pròpies quan l'operació de préstec no és possible.
string[] titolsLlibres = { "Cent anys de solitud", "Rayuela", "Ficcions" };
bool[] disponibilitat = { true, false, true };
void PrestarLlibre(string titolBuscat)
{
int posicioTrobada = -1;
for (int i = 0; i < titolsLlibres.Length; i++)
{
if (titolsLlibres[i] == titolBuscat)
{
posicioTrobada = i;
break;
}
}
if (posicioTrobada == -1)
{
throw new ArgumentException($"El llibre '{titolBuscat}' no existeix al cataleg.");
}
if (!disponibilitat[posicioTrobada])
{
throw new InvalidOperationException($"El llibre '{titolBuscat}' ja esta prestat.");
}
disponibilitat[posicioTrobada] = false;
Console.WriteLine($"Prestec de '{titolBuscat}' registrat correctament.");
}
string[] peticions = { "Rayuela", "1984", "Ficcions" };
foreach (string peticio in peticions)
{
try
{
PrestarLlibre(peticio);
}
catch (ArgumentException ex)
{
Console.WriteLine($"No ha estat possible processar la peticio: {ex.Message}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"No ha estat possible processar la peticio: {ex.Message}");
}
finally
{
Console.WriteLine($"--- Fi del processament de '{peticio}' ---");
}
}Sortida esperada:
No ha estat possible processar la peticio: El llibre 'Rayuela' ja esta prestat. --- Fi del processament de 'Rayuela' --- No ha estat possible processar la peticio: El llibre '1984' no existeix al cataleg. --- Fi del processament de '1984' --- Prestec de 'Ficcions' registrat correctament. --- Fi del processament de 'Ficcions' ---
Observa com el bucle foreach continua processant totes les peticions encara que alguna
d'elles falli: gràcies al try/catch dins del mateix bucle, un error en una petició no
impedeix que es processin les següents, i el finally garanteix que el missatge de tancament
es mostri en els tres casos.
Errors Comuns i Consells
- Capturar
Exceptiongenèrica de forma sistemàtica: amaga el tipus real d'error i dificulta diagnosticar problemes. Reserva-ho com a últim recurs, després dels tipus específics, o per registrar errors veritablement inesperats. - Fer servir excepcions per a validacions que es poden anticipar: si pots comprovar la
condició abans amb un
ifo ambTryParse, fes-ho; no deixis que el programa falli "a propòsit" per després capturar-ho. - Blocs
catchbuits: capturar una excepció i no fer-hi res (ni mostrar un missatge, ni registrar-la) amaga errors que s'haurien de resoldre; com a mínim, informa del problema. - Ordre incorrecte dels
catch: els tipus més específics sempre abans que els més genèrics; en cas contrari, el compilador marcarà el bloc genèric com a inabastable. - Oblidar alliberar recursos: si obres alguna cosa que s'ha de tancar (fitxers,
connexions), fes servir
finallyo les construccions que ja se n'encarreguen automàticament (ho veuràs ambusingal Mòdul 5, en treballar amb fitxers). - Missatges d'excepció poc descriptius:
throw new Exception("Error")no ajuda ningú a entendre què ha passat; inclou sempre dades rellevants (quin llibre, quin valor, quina operació).
Exercicis
-
Escriu un bloc
try/catchque intenti convertir la cadena"cent"aintambint.Parse, capturi l'excepcióFormatExceptioni mostri per consola un missatge indicant que l'entrada no és un número vàlid. -
Crea una funció local
void ValidarISBN(string isbn)que llanci unaArgumentExceptionamb el missatge"El ISBN no pot estar buit."si el paràmetreisbnés una cadena buida (""), i que en cas contrari mostri per consola"ISBN valid.". Crida la funció amb""dins d'untry/catchque capturi l'excepció i mostri el seu missatge. -
Donat l'array
int[] copiesDisponibles = { 2, 0, 3 };, escriu untry/catch/finallyque intenti accedir a la posició5de l'array (que no existeix), capturi l'excepcióIndexOutOfRangeExceptionmostrant un missatge adequat, i alfinallymostri"Consulta finalitzada."independentment de si hi ha hagut error o no.
Solucions
try
{
int numero = int.Parse("cent");
Console.WriteLine(numero);
}
catch (FormatException)
{
Console.WriteLine("Aquesta entrada no es un numero valid.");
}
Com que "cent" no es pot convertir a int, es llança FormatException i el bloc catch
mostra el missatge "Aquesta entrada no es un numero valid."; el programa continua amb
normalitat després del bloc.
void ValidarISBN(string isbn)
{
if (isbn == "")
{
throw new ArgumentException("El ISBN no pot estar buit.");
}
Console.WriteLine("ISBN valid.");
}
try
{
ValidarISBN("");
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
En cridar ValidarISBN(""), es llança l'excepció amb el missatge indicat, que el catch
captura i mostra: "El ISBN no pot estar buit.".
int[] copiesDisponibles = { 2, 0, 3 };
try
{
Console.WriteLine(copiesDisponibles[5]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Aquesta posicio no existeix en aquest array de copies disponibles.");
}
finally
{
Console.WriteLine("Consulta finalitzada.");
}
En intentar accedir a copiesDisponibles[5] (l'array només té les posicions 0, 1 i 2), es
llança IndexOutOfRangeException; el catch mostra el missatge d'error i, a continuació,
el finally mostra "Consulta finalitzada." de totes maneres.
Conclusió
En aquesta lliçó has après què és una excepció i com capturar-la amb try/catch, com
garantir que cert codi s'executi sempre amb finally, com llançar les teves pròpies
excepcions amb throw per assenyalar regles de negoci incomplertes, els tipus d'excepció més
habituals de .NET, com capturar diversos tipus de forma ordenada, i per què no convé abusar de
les excepcions per a validacions que es poden anticipar. BiblioTechConsola ja pot reaccionar
davant de dades no vàlides o situacions imprevistes sense aturar-se de cop.
Amb això es tanca el Mòdul 2: Estructures de Control. Ja saps prendre decisions amb
condicionals, repetir accions amb bucles, classificar valors amb switch, i protegir el teu
programa davant d'errors amb excepcions —tot això, fins ara, fent servir variables soltes,
arrays i funcions locals dins de Main. Aquesta manera de treballar comença a mostrar els seus
límits a mesura que BiblioTechConsola creix: cada llibre són diverses variables soltes
(títol, ISBN, disponibilitat...) que cal mantenir sincronitzades a mà, i cada soci, unes
quantes més. Al Mòdul 3: Programació Orientada a Objectes aprendràs a modelar aquests
conceptes amb classes, agrupant dades i comportament en un únic lloc: el primer pas perquè
BiblioTechConsola deixi de ser un conjunt de variables soltes i comenci a semblar-se a una
aplicació real i ben organitzada.
Curs de Programació en C#
Mòdul 1: Introducció al C#
- Introducció al C#
- Configuració de l'Entorn de Desenvolupament
- Programa Hola Món
- Sintaxi i Estructura Bàsica
- Variables i Tipus de Dades
- Arrays i Cadenes de Text
Mòdul 2: Estructures de Control
Mòdul 3: Programació Orientada a Objectes
- Classes i Objectes
- Mètodes
- Constructors i Destructors
- Herència
- Polimorfisme
- Encapsulació
- Abstracció
- Structs i Records: Tipus per Valor i per Referència
Mòdul 4: Conceptes Avançats de C#
- Interfícies
- Delegats i Esdeveniments
- Pattern Matching i Característiques Modernes de C#
- Genèrics
- Col·leccions
- LINQ (Consulta Integrada en el Llenguatge)
- Programació Asíncrona
Mòdul 5: Treballant amb Dades
- Entrada/Sortida de Fitxers
- Serialització
- Connectivitat amb Bases de Dades
- Entity Framework
- Treball amb JSON i Consum d'APIs REST
Mòdul 6: Temes Avançats
- Reflexió
- Atributs
- Programació Dinàmica
- Gestió de Memòria i Recollida d'Escombraries
- Multifil i Programació Paral·lela
Mòdul 7: Construcció d'Aplicacions
Mòdul 8: Bones Pràctiques i Patrons de Disseny
- Estàndards de Codificació i Bones Pràctiques
- Patrons de Disseny
- Injecció de Dependències i Inversió de Control
- Proves Unitàries
- Revisió i Refactorització de Codi
