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
- Recordatori: array enfront de
List<T> List<T>en profunditatDictionary<TKey, TValue>: cerques per clau- Altres col·leccions:
Queue<T>iStack<T> - Les interfícies darrere de les col·leccions:
IEnumerable<T>iICollection<T> - Construint
Biblioteca:Cataleg,Socis,Prestecs - Ús complet de
Biblioteca
- Recordatori: array enfront de
List<T>
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.
List<T> en profunditat
List<T> en profunditatList<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".
Dictionary<TKey, TValue>: cerques per clau
Dictionary<TKey, TValue>: cerques per clauRecó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 |
- Altres col·leccions:
Queue<T> i Stack<T>
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, ambEnqueue()per afegir iDequeue()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, ambPush()per afegir iPop()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-seTotes 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.
- Les interfícies darrere de les col·leccions:
IEnumerable<T> i ICollection<T>
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.
- Construint
Biblioteca: Cataleg, Socis, Prestecs
Biblioteca: Cataleg, Socis, PrestecsAmb 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,SocisiPrestecssó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ésprivate: és un detall intern d'implementació —un índex per accelerarCercarSociPerId— que no s'hauria d'exposar ni gestionar directament des de fora de la classe;AfegirSocis'encarrega de mantenir-lo sincronitzat ambSocis.- L'esdeveniment
PrestecRegistrat, introduït a la lliçó anterior, es manté sense canvis: ara conviu amb les col·leccions, iRegistrarPrestectant afegeix el préstec aPrestecscom 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
- Ús complet de
Biblioteca
BibliotecaBiblioteca 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>.CountesperantLength: els arrays fan servirLength;List<T>,Dictionary<TKey, TValue>i la resta de col·leccions genèriques fan servirCount. 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çaKeyNotFoundException; fes servirTryGetValue(oContainsKeyabans d'accedir) quan no estiguis segur que la clau existeix. - Oblidar sincronitzar una estructura auxiliar com
_socisPerId: si s'afegís un soci directament aSocis.Add(...)des de fora de la classe sense passar perAfegirSoci, el diccionari quedaria desactualitzat; per aixòSocisés de només lectura i tota alta passa, obligatòriament, perAfegirSoci. - 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 unaList<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
-
Crea una
List<MaterialBibliotecari>amb almenys tres elements (barreja deLlibreiRevista). Afegeix-ne un de nou ambAdd, elimina'n un ambRemove, i mostraCountabans i després de cada operació. -
Crea un
Dictionary<int, Soci>amb almenys tres socis, indexats pel seuId. Fes servirTryGetValueper cercar unIdque existeixi i un altre que no, mostrant un missatge diferent en cada cas sense que el programa llanci cap excepció. -
Construeix la classe
Bibliotecacompleta d'aquest apartat. Dona d'alta dos materials i un soci, subscriu-te a l'esdevenimentPrestecRegistratamb un missatge de confirmació, presta un dels materials i registra'l ambRegistrarPrestec. Comprova quePrestecs.Countpassa a valer1.
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#
- 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
