Amb Biblioteca ja reunint tot el catàleg i tots els socis en col·leccions, la pregunta natural és: com es respon a preguntes com "quins llibres estan disponibles?", "quants materials hi ha de cada autor?" o "quins préstecs porten més de dues setmanes sense retornar-se?" Sense més eines que un foreach, cada pregunta exigiria escriure un bucle manual amb variables auxiliars. LINQ (Language Integrated Query, Consulta Integrada en el Llenguatge) resol això amb un conjunt d'operadors —filtrar, transformar, ordenar, agrupar— que es combinen de forma expressiva i funcionen sobre qualsevol IEnumerable<T> (la interfície que tancava la lliçó anterior). Aquesta lliçó t'ensenya a consultar el catàleg i els préstecs de BiblioTech amb LINQ, deixant enrere els bucles manuals per a aquest tipus de tasques.

Contingut

  1. Què és LINQ i quin problema resol
  2. Sintaxi de mètode: Where, Select, OrderBy
  3. Agrupant resultats: GroupBy
  4. Operadors que retornen un únic valor: FirstOrDefault, Any, Count
  5. Sintaxi de consulta: from...where...select
  6. Execució diferida enfront d'execució immediata
  7. Consultant el catàleg i els préstecs de BiblioTech

  1. Què és LINQ i quin problema resol

LINQ afegeix, a qualsevol col·lecció que implementi IEnumerable<T> (List<T>, arrays, Dictionary<TKey, TValue>.Values, i moltes més), un conjunt de mètodes d'extensió per consultar-la de forma declarativa: en lloc de descriure com recórrer-la pas a pas amb un foreach, descrius què resultat vols obtenir, i LINQ s'encarrega del recorregut per tu.

// Sense LINQ: descriure "com" recorrer, amb un foreach i una llista auxiliar
List<MaterialBibliotecari> disponibles = new List<MaterialBibliotecari>();
foreach (MaterialBibliotecari material in biblioteca.Cataleg)
{
    if (material.Disponible)
    {
        disponibles.Add(material);
    }
}

// Amb LINQ: descriure "que" resultat es vol
List<MaterialBibliotecari> disponiblesLinq = biblioteca.Cataleg.Where(m => m.Disponible).ToList();

Tots dos fragments produeixen el mateix resultat, però el segon expressa directament la intenció ("els materials on Disponible és cert"), sense la mecànica intermèdia del bucle i la llista auxiliar.

  1. Sintaxi de mètode: Where, Select, OrderBy

Els operadors LINQ més usats, en la seva forma habitual (sintaxi de mètode, encadenant crides amb .):

Operador Què fa Exemple
Where Filtra elements segons una condició (Func<T, bool>) cataleg.Where(m => m.Disponible)
Select Transforma cada element en una altra cosa (projecció) cataleg.Select(m => m.Titol)
OrderBy Ordena de forma ascendent segons una clau cataleg.OrderBy(m => m.Titol)
OrderByDescending Ordena de forma descendent cataleg.OrderByDescending(m => m.Titol)
List<MaterialBibliotecari> disponibles = biblioteca.Cataleg
    .Where(m => m.Disponible)
    .OrderBy(m => m.Titol)
    .ToList();

foreach (MaterialBibliotecari material in disponibles)
{
    Console.WriteLine(material.Titol);
}

Aquest encadenat es llegeix d'esquerra a dreta com una canonada: "agafa el catàleg, queda't només amb els disponibles, ordena'ls per títol, i converteix-ho en una llista". Select transforma cada element, útil quan no necessites l'objecte complet sinó només una de les seves dades:

List<string> titols = biblioteca.Cataleg.Select(m => m.Titol).ToList();
// ["Rayuela", "Ficcions", "National Geographic"]

  1. Agrupant resultats: GroupBy

GroupBy reparteix els elements d'una col·lecció en grups segons una clau, cosa que amb un foreach manual requeriria un Dictionary<TClau, List<T>> construït a mà:

var perAutor = biblioteca.Cataleg.GroupBy(m => m.Autor);

foreach (var grup in perAutor)
{
    Console.WriteLine($"{grup.Key}: {grup.Count()} material(s)");
    foreach (MaterialBibliotecari material in grup)
    {
        Console.WriteLine($"  - {material.Titol}");
    }
}

Cada grup és, alhora, la clau (grup.Key, l'autor) i una col·lecció de tots els elements amb aquesta clau, sobre la qual es pot tornar a iterar o aplicar més LINQ. var es fa servir aquí perquè el tipus exacte que retorna GroupBy (IEnumerable<IGrouping<string, MaterialBibliotecari>>) és llarg d'escriure i rarament cal anomenar-lo explícitament.

  1. Operadors que retornen un únic valor: FirstOrDefault, Any, Count

No totes les consultes retornen una col·lecció; algunes responen amb un únic valor:

Operador Què retorna
FirstOrDefault(condicio) El primer element que compleix la condició, o default (normalment null) si cap la compleix
First(condicio) Igual, però llança una excepció si cap element la compleix
Any(condicio) true si almenys un element compleix la condició
Count(condicio) Quants elements compleixen la condició
Count() (sense argument) El nombre total d'elements
MaterialBibliotecari? primerDisponible = biblioteca.Cataleg.FirstOrDefault(m => m.Disponible);

bool hiHaRevistes = biblioteca.Cataleg.Any(m => m is Revista);

int totalLlibres = biblioteca.Cataleg.Count(m => m is Llibre);

Console.WriteLine(primerDisponible?.Titol ?? "Cap disponible");
Console.WriteLine(hiHaRevistes);  // True
Console.WriteLine(totalLlibres);  // 2

FirstOrDefault és preferible a First sempre que "no trobar res" sigui una situació normal i esperable (com aquí); reserva First per a quan l'absència de resultat representaria, en realitat, un error en la lògica del programa.

  1. Sintaxi de consulta: from...where...select

A més de la sintaxi de mètode, C# ofereix una sintaxi de consulta, més semblant a SQL, que el compilador tradueix internament als mateixos mètodes (Where, Select...) vistos abans:

var disponiblesQuery =
    from material in biblioteca.Cataleg
    where material.Disponible
    orderby material.Titol
    select material;

foreach (MaterialBibliotecari material in disponiblesQuery)
{
    Console.WriteLine(material.Titol);
}

Aquesta consulta és exactament equivalent a biblioteca.Cataleg.Where(m => m.Disponible).OrderBy(m => m.Titol): dues maneres diferents d'escriure la mateixa operació. A la pràctica, la sintaxi de mètode és la més habitual en codi C# modern (encaixa millor amb encadenats llargs i amb operadors que no tenen equivalent directe en la sintaxi de consulta, com Count o Any); la sintaxi de consulta es fa servir sobretot quan hi ha diverses fonts combinades (join) o quan l'equip la prefereix per llegibilitat. N'hi ha prou de reconèixer totes dues i saber que són intercanviables.

  1. Execució diferida enfront d'execució immediata

Un detall important, fàcil de passar per alt: la majoria d'operadors LINQ (Where, Select, OrderBy...) no s'executen en el moment en què s'escriuen, sinó quan es recorre el resultat (amb foreach, o en cridar ToList()/ToArray()/Count()). Això s'anomena execució diferida (deferred execution):

var consulta = biblioteca.Cataleg.Where(m => m.Disponible); // encara no s'ha avaluat res

biblioteca.AfegirMaterial(new Llibre("El Aleph", "Jorge Luis Borges", "978-84-376-0497-8"));

foreach (MaterialBibliotecari material in consulta) // AQUI s'executa el filtre, sobre el cataleg ja actualitzat
{
    Console.WriteLine(material.Titol); // inclou tambe "El Aleph", tot i que es va afegir despres de "consulta"
}

Operadors com ToList(), ToArray(), Count() o FirstOrDefault() forcen, en canvi, una execució immediata: el resultat es calcula en aquell moment i queda fixat, sense veure's afectat per canvis posteriors en la col·lecció d'origen. Per a ús quotidià n'hi ha prou de recordar aquesta regla pràctica: si recorreràs el mateix resultat diverses vegades, o necessites "congelar-lo" en un moment concret, afegeix .ToList() al final de la consulta.

  1. Consultant el catàleg i els préstecs de BiblioTech

Amb tot l'anterior, així es responen les preguntes plantejades al principi de la lliçó sobre Biblioteca:

// Quins llibres estan disponibles, ordenats per titol?
List<MaterialBibliotecari> llibresDisponibles = biblioteca.Cataleg
    .Where(m => m is Llibre && m.Disponible)
    .OrderBy(m => m.Titol)
    .ToList();

// Quants materials hi ha de cada autor?
var materialsPerAutor = biblioteca.Cataleg
    .GroupBy(m => m.Autor)
    .Select(g => new { Autor = g.Key, Quantitat = g.Count() });

foreach (var entrada in materialsPerAutor)
{
    Console.WriteLine($"{entrada.Autor}: {entrada.Quantitat}");
}

// Quins prestecs porten mes de 14 dies sense retornar-se (vencuts)?
const int DiesLimitPrestec = 14;

List<Prestec> prestecsVencuts = biblioteca.Prestecs
    .Where(p => p.DataDevolucio == null && (DateTime.Now - p.DataPrestec).Days > DiesLimitPrestec)
    .ToList();

foreach (Prestec prestec in prestecsVencuts)
{
    Console.WriteLine($"Vencut: '{prestec.Llibre.Titol}' prestat a {prestec.Soci.Nom} el {prestec.DataPrestec:yyyy-MM-dd}");
}

materialsPerAutor fa servir una característica que potser encara no havies vist: new { Autor = g.Key, Quantitat = g.Count() } crea un tipus anònim, un objecte d'un sol ús amb només les propietats que necessites mostrar, sense declarar una classe ni un record a part —útil precisament per a resultats intermedis d'una consulta LINQ que no necessiten un tipus amb nom propi a la resta del programa.

La consulta de préstecs vencuts combina dues condicions amb &&: que encara no s'hagi retornat (DataDevolucio == null) i que hagin passat més dies que el límit de préstec des de DataPrestec. És exactament el tipus de pregunta de negoci ("quins préstecs requereixen un avís de retard?") que LINQ permet expressar en poques línies, sense cap bucle manual.

Errors Comuns i Consells

  • Oblidar .ToList() (o similar) quan cal una execució immediata: si modifiques la col·lecció d'origen entre la definició de la consulta i el seu recorregut, el resultat pot canviar de forma inesperada per l'execució diferida; fes servir .ToList() si necessites "fixar" el resultat en un moment concret.
  • Encadenar massa operadors en una sola línia il·legible: encara que tècnicament vàlid, una cadena llarguíssima de .Where().Select().OrderBy().GroupBy()... en una sola línia és difícil de llegir; divideix la consulta en diverses línies, una operació per línia, com als exemples d'aquesta lliçó.
  • Fer servir First quan "no trobat" és una situació normal: First llança InvalidOperationException si cap element compleix la condició; fes servir FirstOrDefault i comprova is not null (recordant la lliçó de Pattern Matching) tret que l'absència de resultat sigui, en efecte, un error de programa.
  • Confondre Count (operador LINQ) amb Count (propietat de List<T>): llista.Count (sense parèntesis) és una propietat ràpida de List<T>; llista.Count(condicio) (amb parèntesis i un predicat) és l'operador LINQ, que recorre la col·lecció per comptar quants elements compleixen la condició. Totes dues coexisteixen sense conflicte perquè tenen signatures diferents.
  • Consell: per a tasques de filtratge, transformació i ordre simples, la sintaxi de mètode és més habitual i més fàcil de depurar pas a pas (pots comentar una línia de la cadena per veure el resultat intermedi); reserva la sintaxi de consulta per quan combinis diverses fonts amb join o quan l'equip la prefereixi explícitament.

Exercicis

  1. Sobre biblioteca.Cataleg, fes servir sintaxi de mètode per obtenir una List<string> amb els títols de tots els materials Disponible, ordenats alfabèticament.

  2. Sobre biblioteca.Cataleg, fes servir GroupBy per agrupar els materials per autor i mostra, per a cada autor, quants materials té al catàleg.

  3. Sobre biblioteca.Prestecs, escriu una consulta que retorni els préstecs sense retornar (DataDevolucio == null) que porten més de 14 dies des de DataPrestec. Prova-ho amb almenys un préstec que compleixi la condició i un altre que no.

Solucions

List<string> titolsDisponibles = biblioteca.Cataleg
    .Where(m => m.Disponible)
    .OrderBy(m => m.Titol)
    .Select(m => m.Titol)
    .ToList();

foreach (string titol in titolsDisponibles)
{
    Console.WriteLine(titol);
}
var perAutor = biblioteca.Cataleg.GroupBy(m => m.Autor);

foreach (var grup in perAutor)
{
    Console.WriteLine($"{grup.Key}: {grup.Count()} material(s)");
}
const int DiesLimitPrestec = 14;

List<Prestec> vencuts = biblioteca.Prestecs
    .Where(p => p.DataDevolucio == null && (DateTime.Now - p.DataPrestec).Days > DiesLimitPrestec)
    .ToList();

foreach (Prestec prestec in vencuts)
{
    Console.WriteLine($"Vencut: '{prestec.Llibre.Titol}' ({prestec.Soci.Nom})");
}

Per comprovar el resultat, es pot crear un Prestec amb una DataPrestec simulada més antiga manualment (recordant que, en el model actual, DataPrestec es fixa automàticament a DateTime.Now al constructor), o simplement raonar sobre préstecs acabats de crear, que mai apareixeran com a vencuts en el mateix instant d'execució.

Conclusió

En aquesta lliçó has après a consultar col·leccions amb LINQ: filtrar amb Where, transformar amb Select, ordenar amb OrderBy, agrupar amb GroupBy, i obtenir valors únics amb FirstOrDefault, Any i Count; també has vist la sintaxi de consulta alternativa i la diferència entre execució diferida i immediata. Biblioteca.Cataleg i Biblioteca.Prestecs ja es poden consultar amb la mateixa expressivitat que una base de dades, sense haver escrit ni una sola línia de SQL.

De fet, aquesta comparació no és casual: al Mòdul 5 (Treballant amb Dades), Entity Framework farà servir LINQ com el seu llenguatge de consulta principal contra una base de dades real, així que tot el que has après aquí es reutilitzarà directament. Abans d'arribar-hi, queda una última peça en aquest mòdul: tot el que s'ha vist fins ara ha estat codi síncron, que bloqueja l'execució mentre es completa. L'última lliçó del mòdul, Programació Asíncrona, introdueix async/await i una primera versió simulada de PrestarLlibreAsync, preparant Biblioteca per a les operacions d'E/S reals (fitxers, bases de dades) que arribaran al proper mòdul.

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