A la lliçó anterior vas aconseguir que Llibre i Revista compartissin, a través de MaterialBibliotecari, les propietats i mètodes comuns a qualsevol material de la biblioteca. Però de moment els continues tractant per separat: si volguessis mostrar la descripció de tots els materials del catàleg, necessitaries dos bucles diferents —un per a llibres i un altre per a revistes— o una sèrie de comprovacions manuals de tipus. El polimorfisme (del grec "moltes formes") és la capacitat de tractar objectes de diferents classes heretades d'una mateixa base de manera uniforme, deixant que cadascun es comporti segons el seu propi tipus real en temps d'execució. En aquesta lliçó aprendràs a recórrer tot el catàleg de BiblioTech —llibres i revistes barrejats— amb un únic bucle, i cada material "sabrà" descriure's a si mateix correctament.

Contingut

  1. Dos tipus de polimorfisme: recordatori del ja vist
  2. Polimorfisme en temps d'execució: el mètode Descriure()
  3. Enllaç dinàmic: qui decideix quina versió s'executa
  4. Un catàleg mixt: MaterialBibliotecari[] recorregut polimòrficament
  5. L'operador is: comprovar el tipus real d'un objecte
  6. L'operador as: convertir de forma segura
  7. Aplicació: filtrar el catàleg per tipus concret

  1. Dos tipus de polimorfisme: recordatori del ja vist

En C# es parla de dues formes de polimorfisme:

Tipus de polimorfisme Quan es decideix quin codi s'executa On l'has vist
En temps de compilació (estàtic) El compilador, segons el nombre/tipus d'arguments Sobrecàrrega de mètodes, lliçó "Mètodes"
En temps d'execució (dinàmic) El programa, en executar-se, segons el tipus real de l'objecte virtual/override, tema d'aquesta lliçó

Ja vas fer servir el primer sense anomenar-lo pel seu nom: quan vas definir MostrarFitxa() i MostrarFitxa(bool incloureIsbn) a la lliçó de Mètodes, el compilador decidia, en el moment de compilar el programa, quina de les dues versions invocar, segons quants arguments li passessis. Aquesta lliçó se centra en el segon tipus, molt més característic de la programació orientada a objectes.

  1. Polimorfisme en temps d'execució: el mètode Descriure()

A la lliçó anterior, MostrarFitxa() ja era virtual a MaterialBibliotecari i es sobreescrivia a Llibre i Revista. Afegirem un segon mètode virtual, Descriure(), pensat específicament per il·lustrar el polimorfisme en acció: en lloc d'imprimir directament per consola, retorna un string amb una descripció breu del material, diferent segons el seu tipus concret.

class MaterialBibliotecari
{
    public string Titol { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; set; } = true;

    public MaterialBibliotecari(string titol, string autor)
    {
        Titol = titol;
        Autor = autor;
    }

    public virtual string Descriure()
    {
        return $"Material: {Titol}, de {Autor}";
    }
}

class Llibre : MaterialBibliotecari
{
    public string Isbn { get; set; }

    public Llibre(string titol, string autor, string isbn) : base(titol, autor)
    {
        Isbn = isbn;
    }

    public override string Descriure()
    {
        return $"Llibre: {Titol}, de {Autor} (ISBN {Isbn})";
    }
}

class Revista : MaterialBibliotecari
{
    public int NumeroEdicio { get; set; }

    public Revista(string titol, string autor, int numeroEdicio) : base(titol, autor)
    {
        NumeroEdicio = numeroEdicio;
    }

    public override string Descriure()
    {
        return $"Revista: {Titol}, edicio numero {NumeroEdicio}";
    }
}

Cada classe ofereix la seva pròpia versió de Descriure(), adaptada al que té sentit mostrar per a aquest tipus concret de material.

  1. Enllaç dinàmic: qui decideix quina versió s'executa

Ara ve la part clau del polimorfisme. Observa aquest codi:

MaterialBibliotecari material = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(material.Descriure());
// Llibre: Rayuela, de Julio Cortazar (ISBN 978-84-376-0495-4)

La variable material està declarada com a tipus MaterialBibliotecari, però l'objecte a què apunta és, en realitat, un Llibre. En cridar material.Descriure(), C# no executa la versió de MaterialBibliotecari (que seria el previsible si només es mirés el tipus de la variable), sinó la versió sobreescrita a Llibre, perquè aquest és el tipus real de l'objecte en memòria. Aquest mecanisme s'anomena enllaç dinàmic (dynamic binding o late binding): quin codi s'executa es decideix en temps d'execució, segons el tipus real de l'objecte, no segons el tipus declarat de la variable que el referencia.

Això només passa perquè Descriure() és virtual a la classe base i està override a la hereva; si Descriure() no fos virtual, material.Descriure() executaria sempre la versió de MaterialBibliotecari, independentment de quin objecte concret contingués realment la variable.

  1. Un catàleg mixt: MaterialBibliotecari[] recorregut polimòrficament

Aquí és on el polimorfisme demostra la seva veritable utilitat: un array (o qualsevol col·lecció) declarat amb el tipus base pot contenir objectes de qualsevol de les seves classes hereves, barrejats, i recórrer-se amb un únic bucle:

MaterialBibliotecari[] cataleg = new MaterialBibliotecari[]
{
    new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"),
    new Revista("National Geographic", "Diversos autors", 302),
    new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1")
};

foreach (MaterialBibliotecari material in cataleg)
{
    Console.WriteLine(material.Descriure());
}

Sortida:

Llibre: Rayuela, de Julio Cortazar (ISBN 978-84-376-0495-4)
Revista: National Geographic, edicio numero 302
Llibre: Ficcions, de Jorge Luis Borges (ISBN 978-84-376-0496-1)

Un únic bucle foreach, escrit una sola vegada sobre el tipus base MaterialBibliotecari, produeix automàticament la descripció correcta per a cada element, sigui Llibre o Revista, sense que el bucle necessiti saber res sobre aquests tipus concrets ni contenir cap comprovació explícita de tipus. Afegir demà una tercera classe hereva (per exemple, un futur Dvd : MaterialBibliotecari) no requeriria tocar aquest bucle en absolut: bastaria que la nova classe sobreescrivís Descriure() a la seva manera. Aquesta és l'essència del polimorfisme: el codi que recorre el catàleg roman estable encara que el catàleg creixi en tipus diferents.

  1. L'operador is: comprovar el tipus real d'un objecte

De vegades, però, necessites fer alguna cosa específica d'un tipus concret, no només allò que ofereix la classe base. L'operador is comprova si un objecte és (o deriva d') un tipus determinat, i opcionalment l'assigna directament a una variable d'aquest tipus més específic:

foreach (MaterialBibliotecari material in cataleg)
{
    if (material is Llibre llibre)
    {
        Console.WriteLine($"Es un llibre; el seu ISBN es {llibre.Isbn}");
    }
    else if (material is Revista revista)
    {
        Console.WriteLine($"Es una revista; el seu numero d'edicio es {revista.NumeroEdicio}");
    }
}

material is Llibre llibre es llegeix com "si material és (en temps d'execució) un Llibre, aleshores assigna aquesta mateixa referència, ja amb el tipus Llibre, a una nova variable anomenada llibre". Dins del bloc if, llibre ja dona accés a Isbn, propietat que no existeix a MaterialBibliotecari i que, per tant, no seria accessible directament a través de la variable material. Aquest patró s'anomena pattern matching amb is, i s'ampliarà força al Mòdul 4 ("Pattern Matching i Característiques Modernes").

  1. L'operador as: convertir de forma segura

L'operador as intenta convertir (castejar) un objecte a un tipus més específic, i retorna null si la conversió no és possible, en lloc de llançar una excepció:

MaterialBibliotecari primerElement = cataleg[0];

Llibre? llibreConvertit = primerElement as Llibre;

if (llibreConvertit != null)
{
    Console.WriteLine($"Conversio correcta: {llibreConvertit.Isbn}");
}
else
{
    Console.WriteLine("L'element no era un Llibre.");
}

Si primerElement fos en realitat una Revista, primerElement as Llibre no llançaria cap error: simplement llibreConvertit valdria null, i el codi pot comprovar-ho amb tranquil·litat. Això contrasta amb una conversió directa entre parèntesis, com (Llibre) primerElement, que si l'objecte no és realment un Llibre, sí que llança una excepció (InvalidCastException) en temps d'execució. A la pràctica moderna de C#, is amb patró (apartat anterior) se sol preferir a as per a aquest tipus de comprovacions, per ser més compacte i llegible, però convé conèixer tots dos.

Operador/tècnica Si el tipus no coincideix Quan usar-lo
(Llibre) material (conversió directa) Llança InvalidCastException Quan estàs segur del tipus i un error seria un senyal real de fallada
material as Llibre Retorna null Quan la conversió pot fallar raonablement i prefereixes comprovar null
material is Llibre llibre La condició de l'if és false (no entra al bloc) La forma més llegible en C# modern; combina comprovació i assignació en un pas

  1. Aplicació: filtrar el catàleg per tipus concret

Tanquem la lliçó combinant el recorregut polimòrfic amb is, per comptar quants elements del catàleg són llibres i quants són revistes —una operació habitual en generar estadístiques del catàleg de BiblioTech:

MaterialBibliotecari[] cataleg = new MaterialBibliotecari[]
{
    new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"),
    new Revista("National Geographic", "Diversos autors", 302),
    new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1"),
    new Revista("Muy Interesante", "Diversos autors", 487)
};

int totalLlibres = 0;
int totalRevistes = 0;

foreach (MaterialBibliotecari material in cataleg)
{
    Console.WriteLine(material.Descriure()); // polimorfisme: cadascu es descriu a la seva manera

    if (material is Llibre)
    {
        totalLlibres++;
    }
    else if (material is Revista)
    {
        totalRevistes++;
    }
}

Console.WriteLine($"Total de llibres: {totalLlibres}");
Console.WriteLine($"Total de revistes: {totalRevistes}");

Nota que a if (material is Llibre), sense declarar cap variable addicional, n'hi ha prou per preguntar "és aquest objecte un Llibre?" sense necessitat d'accedir a cap de les seves propietats específiques —útil quan només interessa comptar o classificar, no operar amb les dades pròpies del tipus concret.

Errors Comuns i Consells

  • Oblidar virtual a la classe base: si Descriure() no és virtual, escriure override a Llibre o Revista provoca un error de compilació; i si en el seu lloc fas servir la paraula clau new per "amagar" el mètode (en lloc de sobreescriure'l), perdràs el polimorfisme: material.Descriure() tornaria a executar sempre la versió de MaterialBibliotecari, encara que l'objecte real fos un Llibre.
  • Castejar directament sense comprovar el tipus: (Llibre) material sense verificar abans amb is pot llançar InvalidCastException si l'objecte real no és un Llibre; prefereix is amb patró o as quan no estiguis segur del tipus real.
  • Escriure un bucle diferent per a cada tipus hereu: si et sorprens escrivint un foreach per a llibres i un altre gairebé idèntic per a revistes, és un senyal que hauries d'estar recorrent el tipus base (MaterialBibliotecari[]) amb un únic bucle polimòrfic, com a l'apartat 4.
  • Confondre el tipus de la variable amb el tipus real de l'objecte: una variable declarada com a MaterialBibliotecari pot apuntar a un Llibre o a una Revista; el que determina quina versió d'un mètode virtual s'executa és sempre el tipus real de l'objecte, mai el tipus amb què es va declarar la variable.
  • Consell: abans de recórrer a is/as per distingir tipus dins d'un bucle, pregunta't si aquest comportament diferent no es podria resoldre simplement sobreescrivint un mètode virtual a cada classe hereva; sol ser un disseny més net i extensible.

Exercicis

  1. Sobre les classes MaterialBibliotecari, Llibre i Revista definides en aquesta lliçó (amb Descriure() com a mètode virtual/override), crea un array MaterialBibliotecari[] amb dos llibres i una revista, i recorre'l amb foreach mostrant el resultat de Descriure() de cadascun.

  2. Sobre el mateix array de l'exercici anterior, fes servir l'operador is amb patró per mostrar, només per als elements que siguin Llibre, un missatge amb el seu ISBN (per exemple: "ISBN: 978-84-376-0495-4").

  3. Escriu una expressió amb l'operador as que intenti convertir el primer element de l'array a Revista, i mostri "Es una revista amb edicio <numero>" si la conversió té èxit, o "No es una revista" si el resultat és null.

Solucions

MaterialBibliotecari[] cataleg = new MaterialBibliotecari[]
{
    new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"),
    new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1"),
    new Revista("National Geographic", "Diversos autors", 302)
};

foreach (MaterialBibliotecari material in cataleg)
{
    Console.WriteLine(material.Descriure());
}
foreach (MaterialBibliotecari material in cataleg)
{
    if (material is Llibre llibre)
    {
        Console.WriteLine($"ISBN: {llibre.Isbn}");
    }
}
Revista? revistaConvertida = cataleg[0] as Revista;

if (revistaConvertida != null)
{
    Console.WriteLine($"Es una revista amb edicio {revistaConvertida.NumeroEdicio}");
}
else
{
    Console.WriteLine("No es una revista");
}

Com que cataleg[0] és en realitat un Llibre (segons l'exercici 1), la conversió amb as retorna null, i el resultat mostrat és "No es una revista".

Conclusió

En aquesta lliçó has vist el polimorfisme en temps d'execució en acció: un mètode virtual com Descriure(), sobreescrit de forma diferent a Llibre i Revista, permet recórrer un catàleg mixt (MaterialBibliotecari[]) amb un únic bucle i obtenir el comportament correcte per a cada objecte segons el seu tipus real —gràcies a l'enllaç dinàmic. També has après a fer servir is i as per accedir puntualment a les particularitats d'un tipus concret quan de veritat ho necessites, sense renunciar al polimorfisme com a estratègia principal.

Fins ara, però, Disponible es pot modificar lliurement des de fora de la classe (material.Disponible = true;, sense passar per Prestar() ni Retornar()), la qual cosa podria deixar el catàleg de BiblioTech en un estat incoherent per error o descuit. A la propera lliçó, Encapsulació, aprendràs a protegir l'estat intern de les teves classes, permetent que només es modifiqui a través dels mètodes pensats per a això.

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