A la lliçó de Polimorfisme (Mòdul 3) ja vas fer servir is Llibre llibre per comprovar el tipus
real d'un MaterialBibliotecari en temps d'execució, i a Sentències Switch (Mòdul 2) vas veure un
primer tast de les switch expressions. Totes dues idees formen part d'una característica més
àmplia del C# modern: el pattern matching, que permet comprovar no només el tipus d'un valor,
sinó també les seves propietats, els seus rangs i combinacions lògiques entre condicions, tot amb
una sintaxi compacta. Aquesta lliçó reuneix el pattern matching, les switch expressions, els
nullable reference types i un recordatori dels records, com el conjunt de característiques que
defineixen l'estil del C# actual, i les aplica per classificar i filtrar materials de BiblioTech.
Contingut
- Recordatori: patrons de tipus amb
is - Patrons de propietat:
{ Disponible: true } - Patrons relacionals i patrons lògics:
and,or,not,>,< - Switch expressions: l'evolució del
switchclàssic - Patrons en switch expressions: combinant tipus i propietat
- Nullable reference types:
#nullable enable,?i l'operador! - Records com a part del C# modern: recordatori i patró posicional
- Recordatori: patrons de tipus amb
is
isA la lliçó de Polimorfisme ja vas escriure codi com aquest, fent servir un patró de tipus (type pattern):
MaterialBibliotecari material = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
if (material is Llibre llibre)
{
Console.WriteLine($"Es un llibre amb ISBN {llibre.Isbn}");
}material is Llibre llibre comprova, en temps d'execució, si material és (o hereta de)
Llibre, i si ho és, declara automàticament una nova variable llibre de tipus Llibre, ja
convertida, disponible dins del bloc. Això ja és pattern matching: un patró de tipus és el més
simple de tots els patrons que ofereix C#, i a partir d'aquí la lliçó presenta patrons més
expressius que es combinen amb ell.
- Patrons de propietat:
{ Disponible: true }
{ Disponible: true }Un patró de propietat (property pattern) comprova el valor d'una o més propietats d'un objecte, sense necessitat d'una variable intermèdia:
if (material is { Disponible: true })
{
Console.WriteLine("El material esta disponible per a prestec.");
}material is { Disponible: true } es llegeix com "si material té una propietat Disponible
igual a true". Aquest patró es pot combinar amb un patró de tipus, comprovant alhora el
tipus concret i una de les seves propietats:
if (material is Llibre { Disponible: true } llibre)
{
Console.WriteLine($"'{llibre.Titol}' es un llibre disponible.");
}Aquí es comproven, en una sola expressió, tres coses alhora: que material sigui (en temps
d'execució) un Llibre, que el seu Disponible sigui true, i a més s'obté la variable
llibre ja convertida, tot en una línia que abans hauria requerit un if imbricat.
- Patrons relacionals i patrons lògics:
and, or, not, >, <
and, or, not, >, <Els patrons relacionals comparen un valor numèric fent servir els operadors <, >, <=,
>= directament dins d'un patró:
Revista revista1 = new Revista("National Geographic", "Varios autores", 302);
if (revista1 is Revista { NumeroEdicio: > 100 })
{
Console.WriteLine("Revista de llarga trajectoria (mes de 100 edicions).");
}Els patrons lògics combinen diversos patrons amb les paraules clau and, or i not (en
lloc de &&, ||, !, reservats per a expressions booleanes normals):
bool esRevistaConsolidada = revista1 is Revista { NumeroEdicio: > 50 and < 1000 };
bool esDisponibleINoRevista = material is not Revista and { Disponible: true };| Patró | Exemple | Significat |
|---|---|---|
| De tipus | material is Llibre |
És (en temps d'execució) un Llibre? |
| De tipus amb variable | material is Llibre llibre |
Igual que l'anterior, i a més assigna llibre |
| De propietat | material is { Disponible: true } |
La propietat Disponible val true? |
| Relacional | numeroEdicio is > 100 |
El valor és més gran que 100? |
Lògic and/or/not |
material is not Revista and { Disponible: true } |
Combina diversos patrons amb lògica |
| Combinat (tipus + propietat) | material is Llibre { Disponible: true } llibre |
Tipus, propietat i variable alhora |
Aquests patrons no es limiten a if: es poden fer servir en qualsevol expressió is, i —el més
habitual en codi modern— dins d'una switch expression, que és el tema del següent apartat.
- Switch expressions: l'evolució del
switch clàssic
switch clàssicA la lliçó de Sentències Switch (Mòdul 2) vas veure un primer tast de les switch expressions:
una manera d'escriure un switch que retorna un valor directament, sense case/break
repetits, fent servir => per a cada resultat possible:
string categoria = "Novella";
string seccio = categoria switch
{
"Novella" => "Narrativa - Planta 1",
"Assaig" => "No ficcio - Planta 2",
"Poesia" => "Poesia - Planta 1",
_ => "Categoria no reconeguda"
};switch clàssic (sentència) |
Switch expression | |
|---|---|---|
| Retorna un valor directament? | No; cal assignar dins de cada case |
Sí; tota l'expressió s'avalua a un valor |
| Paraula clau per cas | case valor: ... break; |
patro => valor, |
| Cas per defecte | default: |
_ (guio baix) |
| Verbositat | Major (claus, break a cada cas) |
Menor (una línia per cas, en general) |
| Compatible amb patrons (tipus, propietat, relacional...) | Limitat | Complet |
La diferència més important per a aquesta lliçó és l'última fila: mentre que el switch clàssic
només compara igualtat exacta contra valors constants, una switch expression admet qualsevol
patró dels vistos als apartats 1 a 3, cosa que la converteix en l'eina natural per classificar
objectes com els de BiblioTech segons el seu tipus real i les seves propietats.
- Patrons en switch expressions: combinant tipus i propietat
Així es classifica un MaterialBibliotecari combinant patrons de tipus i de propietat dins
d'una switch expression:
string Classificar(MaterialBibliotecari material) => material switch
{
Llibre { Disponible: true } => $"Llibre disponible: {material.Titol}",
Llibre => $"Llibre prestat: {material.Titol}",
Revista { NumeroEdicio: > 100 } => $"Revista de llarga trajectoria: {material.Titol}",
Revista => $"Revista: {material.Titol}",
_ => "Tipus de material no reconegut"
};Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Revista revista1 = new Revista("National Geographic", "Varios autores", 302);
llibre1.Prestar();
Console.WriteLine(Classificar(llibre1)); // "Llibre prestat: Rayuela"
Console.WriteLine(Classificar(revista1)); // "Revista de llarga trajectoria: National Geographic"L'ordre dels casos importa, igual que en un switch clàssic: C# avalua cada patró de dalt a baix
i executa el primer que coincideixi. Per això Llibre { Disponible: true } ha d'anar abans que
Llibre a seques: si Llibre fos el primer cas, sempre coincidiria abans de comprovar
Disponible, i el cas més específic mai s'arribaria a aplicar.
Filtrar una llista completa del catàleg reutilitza exactament el mateix patró, aquesta vegada dins d'una expressió booleana amb LINQ (que s'estudiarà en detall en una lliçó posterior d'aquest mòdul):
List<MaterialBibliotecari> catalog = new List<MaterialBibliotecari> { llibre1, revista1 };
foreach (MaterialBibliotecari material in catalog)
{
if (material is Llibre and { Disponible: true })
{
Console.WriteLine($"Disponible per a prestec: {material.Titol}");
}
}
- Nullable reference types:
#nullable enable, ? i l'operador !
#nullable enable, ? i l'operador !Ja vas fer servir Llibre? a la lliçó de Polimorfisme (Llibre? llibreCastejat = primerElement as Llibre;) sense aturar-te en el perquè del signe ?. El C# modern permet activar els
nullable reference types, una característica que fa que el compilador distingeixi, per als
tipus per referència (class, no només string/int?), entre "mai pot ser null" i "pot ser
null", avisant amb advertències quan el codi no respecta aquesta distinció.
S'activa (molts projectes nous de .NET el porten activat per defecte) amb aquesta directiva al principi del fitxer:
Amb aquesta directiva activa, un tipus com Llibre significa "mai null" tret que es declari
explícitament Llibre?:
Llibre? CercarLlibrePerIsbn(List<Llibre> llibres, string isbn)
{
foreach (Llibre llibre in llibres)
{
if (llibre.Isbn == isbn)
{
return llibre;
}
}
return null; // valid: el tipus de retorn es Llibre?, admet null
}Llibre? resultat = CercarLlibrePerIsbn(catalegLlibres, "978-84-376-0495-4");
if (resultat is not null)
{
Console.WriteLine(resultat.Titol); // dins del "if", el compilador sap que no es null
}L'operador ! (anomenat null-forgiving operator, "operador d'indulgencia amb null") li diu al
compilador "confia en mi, se que aquest valor no es null aqui, encara que el seu tipus sigui
nullable", silenciant l'advertència sense afegir cap comprovació real en temps d'execució:
Llibre llibreSegur = CercarLlibrePerIsbn(catalegLlibres, "978-84-376-0495-4")!;
// Advertencia silenciada, pero si el resultat fos realment null, aixo llancaria
// NullReferenceException en temps d'execucio en fer servir llibreSegur mes endavant.| Eina | Què expressa |
|---|---|
Llibre (sense ?) |
El compilador espera que mai sigui null; avisa si detecta el contrari |
Llibre? |
Pot ser null; el compilador exigeix comprovar-ho abans de fer-lo servir amb confiança |
is not null / is null |
Patró recomanat per comprovar nul·litat (més clar que != null) |
valor! |
"Confio que no és null aquí", sense comprovació real; fes-lo servir amb moderació |
És important entendre que els nullable reference types són, sobretot, una ajuda del
compilador en temps de compilació (avisos, no errors durs en la majoria dels casos): no
impedeixen que un null real arribi en temps d'execució si l'operador ! es fa servir de forma
incorrecta, però ajuden enormement a detectar possibles NullReferenceException abans d'executar
el programa.
- Records com a part del C# modern: recordatori i patró posicional
L'última lliçó del Mòdul 3 va presentar els record, amb ResumPrestec com a exemple:
Els records formen part, juntament amb el pattern matching, les switch expressions i els nullable reference types, del conjunt de característiques que defineixen el C# modern: totes comparteixen l'objectiu d'expressar més significat amb menys codi, i de detectar més errors en temps de compilació. Una connexió directa entre records i pattern matching és el patró posicional, que desconstrueix un record directament en variables, fent servir el mateix ordre que el seu constructor:
ResumPrestec resum = new ResumPrestec("Rayuela", "Anna Martinez", new DateTime(2026, 1, 10));
if (resum is ResumPrestec(var titolLlibre, var nomSoci, _))
{
Console.WriteLine($"{nomSoci} te en prestec '{titolLlibre}'");
}ResumPrestec(var titolLlibre, var nomSoci, _) extreu les tres propietats posicionals del
record en dues variables noves, descartant la tercera amb _ (el mateix guió baix que ja coneixes
com a cas "per defecte" d'una switch expression, aquí fet servir com a "no m'interessa aquest
valor"). Aquesta capacitat de desconstrucció automàtica és un altre avantatge que els records
ofereixen "gratis", sense escriure codi addicional, gràcies a la seva sintaxi posicional.
Errors Comuns i Consells
- Oblidar l'ordre en una switch expression amb patrons de tipus: igual que en un
switchclàssic ambcaseapilats, un cas més general abans que un de més específic "tapa" el segon; col·loca sempre els patrons més restrictius (Llibre { Disponible: true }) abans que els més generals (Llibre). - Confondre
and/or/notdels patrons amb&&/||/!de les expressions booleanes: són sintaxis diferents per a contextos diferents; dins d'un patró (is ..., o dins d'uncased'una switch expression) es fan servir les paraulesand/or/not, no els operadors simbòlics. - Activar
#nullable enablea mig projecte i esperar zero advertències: en activar-lo sobre codi ja existent és normal (i esperable) que apareguin avisos en llocs que necessiten revisió; no ignoris aquests avisos sistemàticament, revisa'ls un a un. - Abusar de l'operador
!: fer-lo servir per silenciar qualsevol avís sense analitzar si el valor pot ser realmentnullreintrodueix, per la porta del darrere, el mateix risc deNullReferenceExceptionque els nullable reference types estan pensats per prevenir. - Consell: prefereix
is not nulla!= null(iis nulla== null) en codi modern; a més de ser més llegible,isno es pot sobreescriure de forma sorprenent com sí pot passar, en tipus concrets, amb l'operador==.
Exercicis
-
Escriu un mètode
string Classificar(MaterialBibliotecari material)que faci servir una switch expression amb patrons de tipus i de propietat per retornar:"Llibre disponible"si és unLlibreambDisponible: true,"Llibre prestat"si és unLlibreno disponible,"Revista"en qualsevol altre cas deRevista, i"Desconegut"per a qualsevol altre tipus. Prova'l amb unLlibredisponible, un de prestat i unaRevista. -
Activa
#nullable enableal principi d'un fitxer de prova. Escriu un mètodeLlibre? CercarPerTitol(List<Llibre> llibres, string titol)que retorni el primer llibre elTitoldel qual coincideixi, onullsi no el troba. Crida el mètode dues vegades (un títol que existeixi i un altre que no) i, en cada cas, comprova ambis not nullabans d'accedir a les seves propietats. -
Donat un
record ResumPrestec(string TitolLlibre, string NomSoci, DateTime DataPrestec), crea un objecte d'exemple i fes servir un patró posicional (is ResumPrestec(var titol, var nom, var data)) per extreure els seus tres valors en variables noves i mostrar-los per separat.
Solucions
string Classificar(MaterialBibliotecari material) => material switch
{
Llibre { Disponible: true } => "Llibre disponible",
Llibre => "Llibre prestat",
Revista => "Revista",
_ => "Desconegut"
};
Llibre llibreDisponible = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Llibre llibrePrestat = new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1");
llibrePrestat.Prestar();
Revista revista1 = new Revista("National Geographic", "Varios autores", 302);
Console.WriteLine(Classificar(llibreDisponible)); // "Llibre disponible"
Console.WriteLine(Classificar(llibrePrestat)); // "Llibre prestat"
Console.WriteLine(Classificar(revista1)); // "Revista"
#nullable enable
Llibre? CercarPerTitol(List<Llibre> llibres, string titol)
{
foreach (Llibre llibre in llibres)
{
if (llibre.Titol == titol)
{
return llibre;
}
}
return null;
}
List<Llibre> llibres = new List<Llibre>
{
new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4")
};
Llibre? trobat = CercarPerTitol(llibres, "Rayuela");
if (trobat is not null)
{
Console.WriteLine(trobat.Isbn); // Ok, dins del "if" ja se sap que no es null
}
Llibre? noTrobat = CercarPerTitol(llibres, "Un altre titol");
if (noTrobat is null)
{
Console.WriteLine("No s'ha trobat el llibre.");
}
record ResumPrestec(string TitolLlibre, string NomSoci, DateTime DataPrestec);
ResumPrestec resum = new ResumPrestec("Rayuela", "Anna Martinez", new DateTime(2026, 1, 10));
if (resum is ResumPrestec(var titol, var nom, var data))
{
Console.WriteLine($"Titol: {titol}");
Console.WriteLine($"Soci: {nom}");
Console.WriteLine($"Data: {data:yyyy-MM-dd}");
}
Conclusió
En aquesta lliçó has ampliat l'is i l'switch que ja coneixies amb patrons de tipus, de
propietat, relacionals i lògics; has après a fer servir switch expressions amb aquests mateixos
patrons per classificar objectes de forma compacta; has activat els nullable reference types
perquè el compilador t'ajudi a prevenir referències nul·les; i has vist els records, ja coneguts
del Mòdul 3, desconstruir-se amb un patró posicional. Tot això forma, en conjunt, l'estil
idiomàtic del C# actual.
Fins ara, cada exemple de BiblioTech ha treballat amb objectes individuals o, com a molt, amb
una List<MaterialBibliotecari> improvisada dins d'un mètode. La propera lliçó fa un pas enrere
per introduir els genèrics: la característica que fa possible que List<T> funcioni amb
qualsevol tipus, i que et permetrà escriure les teves pròpies classes i mètodes reutilitzables
per a qualsevol tipus de dada, abans de construir, a la lliçó de Col·leccions, la classe
Biblioteca completa.
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
