A la lliçó d'Arrays i Cadenes de Text (Mòdul 1) vas conèixer List<T> de passada, amb la promesa d'estudiar-la "al Mòdul 4". Aquest moment ha arribat, juntament amb Dictionary<TKey, TValue> i la resta de col·leccions que ofereix .NET. Aquesta lliçó repassa List<T> en profunditat, presenta Dictionary<TKey, TValue> per a cerques ràpides per clau, i —el més important d'aquesta lliçó— construeix finalment la classe Biblioteca completa: el punt central que reunirà tot el catàleg de materials i tots els socis de BiblioTech en memòria, a punt per consultar-se amb LINQ a la propera lliçó.

Contingut

  1. Recordatori: array enfront de List<T>
  2. List<T> en profunditat
  3. Dictionary<TKey, TValue>: cerques per clau
  4. Altres col·leccions: Queue<T> i Stack<T>
  5. Les interfícies darrere de les col·leccions: IEnumerable<T> i ICollection<T>
  6. Construint Biblioteca: Cataleg, Socis, Prestecs
  7. Ús complet de Biblioteca

  1. Recordatori: array enfront de List<T>

La lliçó d'Arrays i Cadenes de Text va deixar plantejada la limitació central dels arrays: la seva mida es fixa en crear-los i no pot canviar després. List<T>, en canvi, creix i minva de forma dinàmica segons se li afegeixen o es treuen elements:

Array (Llibre[]) List<T> (List<Llibre>)
Mida Fixa des de la creació Dinàmica: creix i minva amb Add/Remove
Afegir un element nou No és possible directament; cal crear un array més gran llista.Add(element)
Eliminar un element No és possible directament llista.Remove(element)
Accés per índex array[0] llista[0] (igual de directe)
Genèric Sí, des de la seva declaració (Llibre[]) Sí, List<T> (Mòdul 4)
Quan fer-lo servir Mida coneguda i fixa d'entrada El nombre d'elements canvia durant l'execució (el cas habitual a BiblioTech)

El catàleg d'una biblioteca real creix (es compren llibres nous) i minva (es donen de baixa materials deteriorats) constantment: és exactament l'escenari per al qual List<T>, ara que ja coneixes els genèrics, és l'elecció natural enfront d'un array.

  1. List<T> en profunditat

List<T> (definida a System.Collections.Generic) ofereix un conjunt d'operacions molt més ric que un array:

List<MaterialBibliotecari> cataleg = new List<MaterialBibliotecari>();

cataleg.Add(new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"));
cataleg.Add(new Revista("National Geographic", "Varios autores", 302));

Console.WriteLine(cataleg.Count); // 2 (no "Length", com als arrays)

cataleg.Remove(cataleg[0]); // elimina el primer element (cerca per igualtat de referencia)
Console.WriteLine(cataleg.Count); // 1

cataleg.Insert(0, new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1"));
Console.WriteLine(cataleg[0].Titol); // "Ficcions"

bool conteAlgun = cataleg.Contains(cataleg[0]); // True
Membre Què fa
Add(element) Afegeix un element al final
Remove(element) Elimina la primera aparició d'aquest element
RemoveAt(index) Elimina l'element en aquesta posició
Insert(index, element) Insereix en una posició concreta, desplaçant la resta
Contains(element) La llista conté aquest element exacte?
Count Nombre d'elements actual (equivalent a Length en un array)
llista[index] Accés o assignació directa per índex, igual que en un array

List<T> implementa internament un array que .NET redimensiona automàticament quan cal més espai; això és un detall d'implementació que no cal que gestionis tu mateix: des de fora, List<T> simplement "creix quan cal".

  1. Dictionary<TKey, TValue>: cerques per clau

Recórrer una List<Soci> sencera amb un foreach per trobar el soci amb Id == 3 és vàlid, però ineficient si el catàleg creix molt: cal revisar, en el pitjor cas, tots els elements un a un. Dictionary<TKey, TValue> —l'exemple de genèric amb dos paràmetres de tipus que va tancar la lliçó anterior— resol això associant cada valor a una clau única, amb cerques gairebé instantànies independentment de quants elements contingui:

Dictionary<int, Soci> socisPerId = new Dictionary<int, Soci>();

socisPerId[1] = new Soci(1, "Anna Martinez");
socisPerId[2] = new Soci(2, "Lluis Gomez");

Soci soci = socisPerId[1]; // acces directe per clau, sense recorrer res
Console.WriteLine(soci.Nom); // "Anna Martinez"

Accedir a una clau que no existeix amb socisPerId[99] llança una excepció (KeyNotFoundException); TryGetValue és la manera segura de comprovar-ho sense arriscar-se a aquesta excepció:

if (socisPerId.TryGetValue(99, out Soci? sociTrobat))
{
    Console.WriteLine(sociTrobat.Nom);
}
else
{
    Console.WriteLine("No existeix cap soci amb aquest Id.");
}
Membre Què fa
diccionari[clau] = valor Assigna (o sobreescriu si la clau ja existia)
diccionari[clau] Obté el valor; llança excepció si la clau no existeix
TryGetValue(clau, out valor) Intenta obtenir el valor; retorna false sense excepció si no existeix
ContainsKey(clau) Existeix aquesta clau?
Remove(clau) Elimina l'entrada amb aquesta clau
Keys / Values Col·leccions de totes les claus o tots els valors

  1. Altres col·leccions: Queue<T> i Stack<T>

.NET ofereix altres col·leccions genèriques especialitzades en un ordre concret d'accés, útils en escenaris més específics:

  • Queue<T> (cua, FIFO — first in, first out): el primer element en entrar és el primer a sortir, amb Enqueue() per afegir i Dequeue() per retirar. Encaixa, per exemple, amb una llista d'espera de reserves d'un llibre molt sol·licitat: el primer soci a apuntar-s'hi és el primer a rebre el llibre quan quedi lliure.
  • Stack<T> (pila, LIFO — last in, first out): l'últim element en entrar és el primer a sortir, amb Push() per afegir i Pop() per retirar. Útil per a escenaris de tipus "desfer l'última acció", on importa revertir en ordre invers al que es va fer.
Queue<Soci> llistaEspera = new Queue<Soci>();
llistaEspera.Enqueue(new Soci(1, "Anna Martinez")); // s'apunta primer
llistaEspera.Enqueue(new Soci(2, "Lluis Gomez"));   // s'apunta despres

Soci seguentSoci = llistaEspera.Dequeue(); // "Anna Martinez": la primera a apuntar-se

Totes dues s'esmenten aquí perquè les reconeguis si apareixen en codi d'altri; la resta d'aquest mòdul se centra en List<T> i Dictionary<TKey, TValue>, que són, de llarg, les col·leccions més utilitzades en el dia a dia.

  1. Les interfícies darrere de les col·leccions: IEnumerable<T> i ICollection<T>

List<T>, Dictionary<TKey, TValue>, Queue<T> i Stack<T> no són tipus aïllats: totes implementen un conjunt comú d'interfícies (recorda la lliçó d'Interfícies) que defineixen capacitats compartides:

Interfície Què garanteix
IEnumerable<T> Es pot recórrer amb foreach; és el mínim comú de totes les col·leccions
ICollection<T> A més de recórrer-se, es pot consultar Count, i afegir/treure elements
IList<T> A més de l'anterior, es pot accedir per índex (colleccio[i])

Aquesta jerarquia d'interfícies és exactament la raó per la qual foreach funciona igual sobre un array, una List<T>, o les claus d'un Dictionary<TKey, TValue>: totes implementen, com a mínim, IEnumerable<T>. I és també la raó per la qual, a la propera lliçó, LINQ podrà operar indistintament sobre qualsevol d'elles: la majoria de les seves operacions estan definides sobre IEnumerable<T>, no sobre List<T> en particular.

  1. Construint Biblioteca: Cataleg, Socis, Prestecs

Amb List<T> i Dictionary<TKey, TValue> ja dominats, es completa la classe Biblioteca, que a la lliçó de Delegats i Esdeveniments va aparèixer només amb el seu esdeveniment. Aquesta és la seva versió completa, amb les tres col·leccions centrals del model i un índex per Id per a cerques ràpides de socis:

class Biblioteca
{
    public List<MaterialBibliotecari> Cataleg { get; } = new List<MaterialBibliotecari>();
    public List<Soci> Socis { get; } = new List<Soci>();
    public List<Prestec> Prestecs { get; } = new List<Prestec>();

    private Dictionary<int, Soci> _socisPerId = new Dictionary<int, Soci>();

    public event Action<Prestec> PrestecRegistrat;

    public void AfegirMaterial(MaterialBibliotecari material)
    {
        Cataleg.Add(material);
    }

    public void AfegirSoci(Soci soci)
    {
        Socis.Add(soci);
        _socisPerId[soci.Id] = soci;
    }

    public Soci? CercarSociPerId(int id)
    {
        _socisPerId.TryGetValue(id, out Soci? soci);
        return soci;
    }

    public void RegistrarPrestec(Prestec prestec)
    {
        Prestecs.Add(prestec);
        PrestecRegistrat?.Invoke(prestec);
    }
}

Algunes decisions de disseny mereixen explicar-se:

  • Cataleg, Socis i Prestecs són propietats de només lectura ({ get; }, recordant la lliçó d'Encapsulació del Mòdul 3): la referència a cada llista no es pot reassignar des de fora (biblioteca.Cataleg = altraLlista; no compila), però el seu contingut sí que es pot modificar a través dels mètodes pensats per a això (AfegirMaterial, AfegirSoci, RegistrarPrestec).
  • _socisPerId és private: és un detall intern d'implementació —un índex per accelerar CercarSociPerId— que no s'hauria d'exposar ni gestionar directament des de fora de la classe; AfegirSoci s'encarrega de mantenir-lo sincronitzat amb Socis.
  • L'esdeveniment PrestecRegistrat, introduït a la lliçó anterior, es manté sense canvis: ara conviu amb les col·leccions, i RegistrarPrestec tant afegeix el préstec a Prestecs com avisa qui hi estigui subscrit.
classDiagram
    class Biblioteca {
        +List~MaterialBibliotecari~ Cataleg
        +List~Soci~ Socis
        +List~Prestec~ Prestecs
        -Dictionary~int, Soci~ _socisPerId
        +event PrestecRegistrat
        +AfegirMaterial(MaterialBibliotecari)
        +AfegirSoci(Soci)
        +CercarSociPerId(int) Soci
        +RegistrarPrestec(Prestec)
    }
    Biblioteca --> "*" MaterialBibliotecari
    Biblioteca --> "*" Soci
    Biblioteca --> "*" Prestec

  1. Ús complet de Biblioteca

Biblioteca biblioteca = new Biblioteca();
biblioteca.PrestecRegistrat += prestec =>
    Console.WriteLine($"S'ha prestat '{prestec.Llibre.Titol}' a {prestec.Soci.Nom}");

biblioteca.AfegirMaterial(new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"));
biblioteca.AfegirMaterial(new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1"));
biblioteca.AfegirMaterial(new Revista("National Geographic", "Varios autores", 302));

biblioteca.AfegirSoci(new Soci(1, "Anna Martinez"));
biblioteca.AfegirSoci(new Soci(2, "Lluis Gomez"));

Console.WriteLine(biblioteca.Cataleg.Count); // 3
Console.WriteLine(biblioteca.Socis.Count);   // 2

Soci? soci1 = biblioteca.CercarSociPerId(1);
if (soci1 is not null && biblioteca.Cataleg[0] is Llibre llibre1)
{
    llibre1.Prestar();
    biblioteca.RegistrarPrestec(new Prestec(llibre1, soci1));
    // "S'ha prestat 'Rayuela' a Anna Martinez"
}

A partir d'aquesta versió, Biblioteca és el punt únic d'entrada a l'estat complet de l'aplicació en memòria: tot el catàleg, tots els socis, i l'historial de préstecs, cadascun accessible com una col·lecció de .NET completament estàndard.

Errors Comuns i Consells

  • Fer servir List<T>.Count esperant Length: els arrays fan servir Length; List<T>, Dictionary<TKey, TValue> i la resta de col·leccions genèriques fan servir Count. Confondre'ls és un error de compilació fàcil de corregir, però freqüent en començar.
  • Accedir a una clau inexistent amb diccionari[clau]: llança KeyNotFoundException; fes servir TryGetValue (o ContainsKey abans d'accedir) quan no estiguis segur que la clau existeix.
  • Oblidar sincronitzar una estructura auxiliar com _socisPerId: si s'afegís un soci directament a Socis.Add(...) des de fora de la classe sense passar per AfegirSoci, el diccionari quedaria desactualitzat; per això Socis és de només lectura i tota alta passa, obligatòriament, per AfegirSoci.
  • Triar la col·lecció equivocada per al patró d'accés: si necessites cercar constantment per un identificador únic, un Dictionary és molt més eficient que recórrer una List<T> sencera cada vegada; si només necessites recórrer-ho tot en ordre, List<T> n'hi ha prou.
  • Consell: abans de triar una col·lecció, pregunta't com hi accediràs a les dades amb més freqüència (per posició?, per clau?, sempre el primer a entrar?); la resposta assenyala gairebé sempre la col·lecció correcta.

Exercicis

  1. Crea una List<MaterialBibliotecari> amb almenys tres elements (barreja de Llibre i Revista). Afegeix-ne un de nou amb Add, elimina'n un amb Remove, i mostra Count abans i després de cada operació.

  2. Crea un Dictionary<int, Soci> amb almenys tres socis, indexats pel seu Id. Fes servir TryGetValue per cercar un Id que existeixi i un altre que no, mostrant un missatge diferent en cada cas sense que el programa llanci cap excepció.

  3. Construeix la classe Biblioteca completa d'aquest apartat. Dona d'alta dos materials i un soci, subscriu-te a l'esdeveniment PrestecRegistrat amb un missatge de confirmació, presta un dels materials i registra'l amb RegistrarPrestec. Comprova que Prestecs.Count passa a valer 1.

Solucions

List<MaterialBibliotecari> materials = new List<MaterialBibliotecari>
{
    new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"),
    new Revista("National Geographic", "Varios autores", 302),
    new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1")
};

Console.WriteLine(materials.Count); // 3

materials.Add(new Revista("Muy Interesante", "Varios autores", 45));
Console.WriteLine(materials.Count); // 4

materials.Remove(materials[0]);
Console.WriteLine(materials.Count); // 3
Dictionary<int, Soci> socisPerId = new Dictionary<int, Soci>
{
    [1] = new Soci(1, "Anna Martinez"),
    [2] = new Soci(2, "Lluis Gomez"),
    [3] = new Soci(3, "Marta Lopez")
};

if (socisPerId.TryGetValue(2, out Soci? trobat))
{
    Console.WriteLine($"Trobat: {trobat.Nom}"); // "Lluis Gomez"
}

if (!socisPerId.TryGetValue(99, out Soci? noTrobat))
{
    Console.WriteLine("No existeix cap soci amb Id 99.");
}
Biblioteca biblioteca = new Biblioteca();
biblioteca.PrestecRegistrat += p =>
    Console.WriteLine($"S'ha prestat '{p.Llibre.Titol}' a {p.Soci.Nom}");

biblioteca.AfegirMaterial(new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"));
biblioteca.AfegirMaterial(new Revista("National Geographic", "Varios autores", 302));
biblioteca.AfegirSoci(new Soci(1, "Anna Martinez"));

Soci? soci = biblioteca.CercarSociPerId(1);
if (soci is not null && biblioteca.Cataleg[0] is Llibre llibre)
{
    llibre.Prestar();
    biblioteca.RegistrarPrestec(new Prestec(llibre, soci));
}

Console.WriteLine(biblioteca.Prestecs.Count); // 1

Conclusió

En aquesta lliçó has aprofundit en List<T>, conegut Dictionary<TKey, TValue> per a cerques ràpides per clau, vist de passada Queue<T> i Stack<T>, i entès que totes aquestes col·leccions comparteixen un conjunt comú d'interfícies (IEnumerable<T>, ICollection<T>). Sobretot, ha quedat completa la classe Biblioteca: Cataleg, Socis i Prestecs com a col·leccions centrals, un índex per Id per a cerques ràpides de socis, i l'esdeveniment PrestecRegistrat convivint amb tota la resta.

Tenir el catàleg complet en una List<MaterialBibliotecari> obre la porta a l'eina més potent d'aquest mòdul per treballar amb col·leccions: LINQ. La propera lliçó t'ensenyarà a filtrar, ordenar i agrupar biblioteca.Cataleg i biblioteca.Prestecs amb una sintaxi expressiva i compacta, deixant definitivament enrere els bucles foreach manuals per a aquest tipus de consultes.

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