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

  1. Què és una excepció?
  2. try/catch: capturar errors
  3. finally: codi que sempre s'executa
  4. Llançar excepcions pròpies amb throw
  5. Tipus d'excepció habituals
  6. Capturar múltiples tipus d'excepció
  7. Bones pràctiques amb excepcions
  8. Aplicació completa: deixar un llibre de forma segura

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

  1. try/catch: capturar errors

El 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}");
}

  1. finally: codi que sempre s'executa

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

  1. Llançar excepcions pròpies amb throw

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

  1. 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}");
}

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

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

  1. 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 Exception genè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 if o amb TryParse, fes-ho; no deixis que el programa falli "a propòsit" per després capturar-ho.
  • Blocs catch buits: 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 finally o les construccions que ja se n'encarreguen automàticament (ho veuràs amb using al 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

  1. Escriu un bloc try/catch que intenti convertir la cadena "cent" a int amb int.Parse, capturi l'excepció FormatException i mostri per consola un missatge indicant que l'entrada no és un número vàlid.

  2. Crea una funció local void ValidarISBN(string isbn) que llanci una ArgumentException amb el missatge "El ISBN no pot estar buit." si el paràmetre isbn és una cadena buida (""), i que en cas contrari mostri per consola "ISBN valid.". Crida la funció amb "" dins d'un try/catch que capturi l'excepció i mostri el seu missatge.

  3. Donat l'array int[] copiesDisponibles = { 2, 0, 3 };, escriu un try/catch/finally que intenti accedir a la posició 5 de l'array (que no existeix), capturi l'excepció IndexOutOfRangeException mostrant un missatge adequat, i al finally mostri "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#

Mòdul 2: Estructures de Control

Mòdul 3: Programació Orientada a Objectes

Mòdul 4: Conceptes Avançats de C#

Mòdul 5: Treballant amb Dades

Mòdul 6: Temes Avançats

Mòdul 7: Construcció d'Aplicacions

Mòdul 8: Bones Pràctiques i Patrons de Disseny

Mòdul 9: Projecte Final

© Copyright 2026. Tots els drets reservats