Al llarg d'aquest mòdul, tot el que has modelat —MaterialBibliotecari, Llibre,
Revista, Soci, Prestec— són classes: tipus per referència, pensats per
representar entitats amb identitat pròpia i comportament (recorda Prestar(),
Retornar(), Descriure()). Però no totes les dades de BiblioTech encaixen bé en
aquest motlle: l'adreça postal d'un soci, o el rang de dates d'un préstec, són dades
petites, sense comportament propi rellevant, on el que importa és el valor que
contenen, no la identitat de l'objecte que les representa. Aquesta última lliçó del mòdul
tanca el cercle de l'orientació a objectes presentant els structs (tipus per valor) i els
records (una forma moderna i concisa de modelar dades immutables), i t'ajudarà a decidir
quan cadascun és més adequat que una classe tradicional.
Contingut
- Recordatori: tipus per valor enfront de tipus per referència
struct: definir un tipus per valor- Quan usar
struct: dades petites i immutables record: igualtat per valor i impressió llegible per a classesrecord struct: el millor de tots dos mons- Comparació completa:
class,struct,record,record struct - Aplicant l'elecció correcta a BiblioTech
- Recordatori: tipus per valor enfront de tipus per referència
A la primera lliçó del mòdul vas veure que les classes són tipus per referència: copiar
una variable de tipus Llibre no duplica l'objecte, sinó que crea una segona variable que
apunta al mateix objecte en memòria.
Llibre original = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Llibre copia = original;
copia.Titol = "Un altre titol";
Console.WriteLine(original.Titol); // "Un altre titol" -- l'original tambe ha canviatUn tipus per valor, en canvi, es comporta de forma oposada: en copiar una variable, es
copien totes les seves dades, creant una segona instància completament independent.
Modificar la còpia mai afecta l'original. Els tipus primitius que ja coneixes (int,
double, bool, char) són tipus per valor; en aquesta lliçó aprendràs a crear els teus
propis tipus per valor amb struct.
Tipus per referència (class) |
Tipus per valor (struct) |
|
|---|---|---|
| En copiar una variable | Es copia la referència; totes dues variables apunten al mateix objecte | Es copien les dades; són dues instàncies independents |
| Modificar la còpia afecta l'original | Sí | No |
| Exemples ja vistos | Llibre, Soci, Prestec |
int, double, bool, DateTime |
struct: definir un tipus per valor
struct: definir un tipus per valorUn struct es defineix de forma molt semblant a una classe, canviant la paraula clau
class per struct:
struct Adreca
{
public string Carrer;
public string Ciutat;
public string CodiPostal;
public Adreca(string carrer, string ciutat, string codiPostal)
{
Carrer = carrer;
Ciutat = ciutat;
CodiPostal = codiPostal;
}
public override string ToString()
{
return $"{Carrer}, {Ciutat} ({CodiPostal})";
}
}I s'utilitza exactament igual que una classe en aparença:
Adreca adrecaSoci = new Adreca("Carrer Major 10", "Barcelona", "08013");
Console.WriteLine(adrecaSoci); // Carrer Major 10, Barcelona (08013)Però el seu comportament en copiar-lo és el d'un tipus per valor:
Adreca adrecaOriginal = new Adreca("Carrer Major 10", "Barcelona", "08013");
Adreca adrecaCopiada = adrecaOriginal; // es copien TOTES les dades, no una referencia
adrecaCopiada.Ciutat = "Girona";
Console.WriteLine(adrecaOriginal.Ciutat); // "Barcelona" -- l'original NO ha canviat
Console.WriteLine(adrecaCopiada.Ciutat); // "Girona"A diferència del que passava amb Llibre (una classe) al principi d'aquesta lliçó, modificar
adrecaCopiada no afecta en absolut adrecaOriginal: són dos structs independents, cadascun
amb la seva pròpia còpia de les dades.
Ara es pot afegir aquesta adreça al model de Soci:
class Soci
{
public int Id { get; }
public string Nom { get; set; }
public Adreca Adreca { get; set; }
public Soci(int id, string nom)
{
Id = id;
Nom = nom;
}
}Soci soci1 = new Soci(1, "Anna Martinez");
soci1.Adreca = new Adreca("Carrer Major 10", "Barcelona", "08013");
Console.WriteLine(soci1.Adreca); // Carrer Major 10, Barcelona (08013)
- Quan usar
struct: dades petites i immutables
struct: dades petites i immutablesNo tot hauria de ser un struct: són adequats en un conjunt concret de situacions. La regla
pràctica més citada a la documentació de .NET és preferir struct quan es compleixen, alhora,
diverses condicions:
- El tipus representa un únic valor conceptual petit (pocs camps), com una adreça, una coordenada o un rang de dates.
- Les seves instàncies són, idealment, immutables (no canvien després de crear-se) o canvien amb poca freqüència.
- No necessita participar en una jerarquia d'herència (un
structno pot heretar d'un altrestructni d'una classe, tot i que sí pot implementar interfícies, que veuràs al Mòdul 4). - Copiar-lo repetidament no suposa un cost de rendiment rellevant (structs molt grans poden ser més costosos de copiar que passar una referència).
Un segon exemple útil a BiblioTech és un rang de dates, per exemple per representar el període previst d'un préstec:
struct RangDates
{
public DateTime Inici;
public DateTime Fi;
public RangDates(DateTime inici, DateTime fi)
{
Inici = inici;
Fi = fi;
}
public int DuracioEnDies()
{
return (Fi - Inici).Days;
}
}RangDates periodePrestec = new RangDates(new DateTime(2026, 1, 10), new DateTime(2026, 1, 24));
Console.WriteLine($"Durada del prestec: {periodePrestec.DuracioEnDies()} dies"); // 14 dies| Senyal | struct o class? |
|---|---|
| Dada petita, pocs camps, sense identitat pròpia rellevant (una adreça, un rang de dates) | struct |
Necessita herència d'altres classes, o representa una entitat amb identitat pròpia (Llibre, Soci, Prestec) |
class |
| Es copiarà amb molta freqüència i és molt petit | struct (copiar és barat) |
| Es compartirà i modificarà des de diverses parts del programa, esperant que els canvis es vegin a tot arreu | class (el comportament per referència és exactament el que es busca) |
record: igualtat per valor i impressió llegible per a classes
record: igualtat per valor i impressió llegible per a classesA més de struct, el C# modern ofereix els records, pensats per modelar dades de forma
concisa i immutable, principalment amb dos avantatges automàtics enfront d'una classe
tradicional: igualtat per valor i una implementació de ToString() llegible, totes dues
generades pel compilador sense que hagis d'escriure-les tu.
Aquesta única línia —fent servir l'anomenada sintaxi posicional, amb els paràmetres entre
parèntesis al costat del nom del record— ja defineix tres propietats de només lectura
(TitolLlibre, NomSoci, DataPrestec), un constructor que les rep totes, un ToString()
llegible i una comparació d'igualtat basada en el valor de les seves propietats, tot generat
automàticament.
ResumPrestec resum1 = new ResumPrestec("Rayuela", "Anna Martinez", new DateTime(2026, 1, 10));
ResumPrestec resum2 = new ResumPrestec("Rayuela", "Anna Martinez", new DateTime(2026, 1, 10));
Console.WriteLine(resum1);
// ResumPrestec { TitolLlibre = Rayuela, NomSoci = Anna Martinez, DataPrestec = 10/01/2026 0:00:00 }
Console.WriteLine(resum1 == resum2); // True: mateixa informacio, encara que son dos objectes diferents en memoriaCompara això amb el que passaria si ResumPrestec fos una classe tradicional: ==
compararia si totes dues variables apunten al mateix objecte en memòria (igualtat per
referència), i resum1 == resum2 donaria false, encara que continguessin exactament les
mateixes dades, perquè new hauria creat dos objectes diferents. Els records redefineixen
== (i Equals) per comparar, en el seu lloc, el contingut: dos records són iguals si
totes les seves propietats ho són.
ResumPrestec és un bon exemple d'ús real: és una dada de només lectura, pensada per
mostrar o exportar un resum d'un préstec (per exemple, a partir d'un objecte Prestec
existent), sense necessitat de comportament propi ni d'identitat mutable —just el perfil
que un record modela millor que una classe tradicional.
ResumPrestec CrearResum(Prestec prestec)
{
return new ResumPrestec(prestec.Llibre.Titol, prestec.Soci.Nom, prestec.DataPrestec);
}Cal notar que, per defecte, un record (a diferència d'un struct) continua sent un tipus
per referència per sota: el que aporta no és el comportament de còpia per valor, sinó la
igualtat per valor i el ToString() automàtic.
record struct: el millor de tots dos mons
record struct: el millor de tots dos monsEl C# també permet combinar totes dues idees amb record struct: un tipus que és, alhora,
un tipus per valor (com struct) i que obté automàticament la igualtat per valor i el
ToString() generat (com record). Recuperant l'exemple d'Adreca de l'apartat 2, aquesta
seria la seva versió com a record struct:
Aquesta única línia substitueix per complet l'struct Adreca escrit a mà a l'apartat 2:
mateix comportament de còpia per valor, però amb constructor, ToString() i igualtat
generats automàticament, sense escriure ni una línia més.
Adreca adreca1 = new Adreca("Carrer Major 10", "Barcelona", "08013");
Adreca adreca2 = new Adreca("Carrer Major 10", "Barcelona", "08013");
Console.WriteLine(adreca1); // Adreca { Carrer = Carrer Major 10, Ciutat = Barcelona, CodiPostal = 08013 }
Console.WriteLine(adreca1 == adreca2); // True: igualtat per valor, a mes de comportament de struct en copiar
- Comparació completa:
class, struct, record, record struct
class, struct, record, record structclass |
struct |
record (class) |
record struct |
|
|---|---|---|---|---|
| Comportament en copiar | Per referència | Per valor | Per referència | Per valor |
Igualtat (==) per defecte |
Per referència (mateix objecte) | Per valor dels seus camps | Per valor de les seves propietats | Per valor de les seves propietats |
ToString() llegible generat automàticament |
No (cal escriure'l) | No (cal escriure'l) | Sí | Sí |
| Suporta herència de classes | Sí | No | Sí (entre records) | No |
| Ús típic a BiblioTech | Entitats amb identitat i comportament: Llibre, Soci, Prestec |
Dades petites per valor amb comportament propi: RangDates |
Dades immutables per mostrar/exportar: ResumPrestec |
Dades petites i immutables, amb igualtat i ToString() gratis: Adreca |
- Aplicant l'elecció correcta a BiblioTech
Amb tot el que s'ha vist al mòdul, així queda repartit el model de domini complet de BiblioTech entre les diferents eines del C#:
classDiagram
class MaterialBibliotecari {
<<abstract>>
+string Titol
+string Autor
+bool Disponible
+Prestar()
+Retornar()
+Descriure()* string
}
class Llibre {
+string Isbn
}
class Revista {
+int NumeroEdicio
}
class Soci {
+int Id
+string Nom
+Adreca Adreca
}
class Prestec {
+Llibre Llibre
+Soci Soci
+DateTime DataPrestec
+DateTime? DataDevolucio
+RegistrarDevolucio()
}
MaterialBibliotecari <|-- Llibre
MaterialBibliotecari <|-- Revista
Prestec --> Llibre
Prestec --> Soci
Soci --> Adreca
- Classes (
MaterialBibliotecari,Llibre,Revista,Soci,Prestec): entitats amb identitat pròpia, estat mutable controlat (encapsulació) i comportament (Prestar(),Descriure()...). El nucli del model, construït a les lliçons anteriors. struct(RangDates): un valor petit i autocontingut, sense identitat pròpia, usat per valor allà on calgui calcular una durada.record struct(Adreca): una dada petita i immutable associada a unSoci, on interessa tant el comportament de còpia per valor com la igualtat i elToString()automàtics.record(ResumPrestec): una instantània immutable d'un préstec, pensada per mostrar o exportar informació, sense necessitat de comportament ni d'identitat mutable.
Errors Comuns i Consells
- Usar
structper a una entitat amb identitat pròpia: si un tipus necessita comportament ric, mutabilitat lliure o participar en una jerarquia d'herència (comLlibreoPrestec), ha de ser unaclass, no unstruct; forçar unstructaquí generaria còpies inesperades de dades que haurien de compartir-se. - Sorprendre's perquè una còpia d'un
structno reflecteix els canvis: és precisament el comportament esperat d'un tipus per valor; si necessites que dues variables comparteixin la mateixa dada i totes dues vegin els canvis, necessites unaclass, no unstruct. - Confondre
recordambrecord struct: unrecorda seques continua sent un tipus per referència (guanya igualtat per valor iToString(), però no el comportament de còpia); si a més vols el comportament de còpia per valor, necessitesrecord struct. - Definir structs molt grans: si un
structacumula molts camps, copiar-lo deixa de ser barat i pot perjudicar el rendiment; en aquest cas, unaclass(o repensar el disseny) sol ser una millor opció. - Consell: davant el dubte entre
classistruct/record struct, pregunta't si el tipus representa "un valor" (una data, una adreça, una coordenada) o "una entitat" (alguna cosa amb identitat pròpia que canvia al llarg del temps, com un soci o un préstec); la resposta gairebé sempre assenyala l'elecció correcta.
Exercicis
-
Defineix un
struct RangDatesamb les propietatsIniciiFi(totes duesDateTime), un constructor que les rebi, i un mètodeint DuracioEnDies()que retorni la diferència en dies entre totes dues dates. Crea dos rangs de dates diferents i mostra la seva durada. -
Defineix
record struct Adreca(string Carrer, string Ciutat, string CodiPostal);i compara-la amb la versió manual de l'apartat 2: crea dues adreces amb les mateixes dades fent servir elrecord structi comprova amb==que es consideren iguals, i mostra'n una directament ambConsole.WriteLineper comprovar elToString()automàtic. -
Defineix
record ResumPrestec(string TitolLlibre, string NomSoci, DateTime DataPrestec);. Crea dos objectesResumPrestecamb exactament els mateixos valors però creats per separat ambnew, i comprova amb==que, malgrat ser dos objectes diferents en memòria, es consideren iguals.
Solucions
struct RangDates
{
public DateTime Inici;
public DateTime Fi;
public RangDates(DateTime inici, DateTime fi)
{
Inici = inici;
Fi = fi;
}
public int DuracioEnDies()
{
return (Fi - Inici).Days;
}
}
RangDates rang1 = new RangDates(new DateTime(2026, 1, 10), new DateTime(2026, 1, 24));
RangDates rang2 = new RangDates(new DateTime(2026, 2, 1), new DateTime(2026, 2, 5));
Console.WriteLine(rang1.DuracioEnDies()); // 14
Console.WriteLine(rang2.DuracioEnDies()); // 4
record struct Adreca(string Carrer, string Ciutat, string CodiPostal);
Adreca adreca1 = new Adreca("Carrer Major 10", "Barcelona", "08013");
Adreca adreca2 = new Adreca("Carrer Major 10", "Barcelona", "08013");
Console.WriteLine(adreca1 == adreca2); // True
Console.WriteLine(adreca1); // Adreca { Carrer = Carrer Major 10, Ciutat = Barcelona, CodiPostal = 08013 }
record ResumPrestec(string TitolLlibre, string NomSoci, DateTime DataPrestec);
ResumPrestec resum1 = new ResumPrestec("Ficcions", "Lluis Gomez", new DateTime(2026, 3, 5));
ResumPrestec resum2 = new ResumPrestec("Ficcions", "Lluis Gomez", new DateTime(2026, 3, 5));
Console.WriteLine(resum1 == resum2); // True
Encara que resum1 i resum2 es creen per separat amb new (i, per tant, ocupen
posicions diferents en memòria), l'operador == d'un record compara les seves propietats
una a una, així que el resultat és True.
Conclusió
Amb aquesta lliçó es completa el Mòdul 3: Programació Orientada a Objectes. Has après a
definir classes i crear objectes, a dotar-los de comportament amb mètodes, a garantir la
seva correcta inicialització amb constructors, a compartir codi mitjançant herència, a
tractar diferents tipus de forma uniforme amb polimorfisme, a protegir el seu estat intern
amb encapsulació, a expressar conceptes purament base amb classes abstractes, i, en aquesta
última lliçó, a triar entre class, struct, record i record struct segons si el que
modeles és una entitat amb identitat o un simple valor. El model de domini de BiblioTech
queda ara format per MaterialBibliotecari (abstracta), Llibre i Revista (les seves
hereves concretes), Soci (amb la seva Adreca), Prestec, i tipus de suport com
RangDates i ResumPrestec.
Aquest model, amb les seves classes, propietats i mètodes ja definits, és exactament el que
reutilitzaràs al Mòdul 4: Conceptes Avançats de C#: allà aprendràs a definir
interfícies per expressar capacitats compartides sense herència de classe, a fer servir
delegats i esdeveniments per reaccionar a canvis (com que un llibre passi a estar
disponible), a aprofitar el pattern matching modern per simplificar l'ús d'is/as que
ja coneixes, a escriure codi genèric amb genèrics, i sobretot a emmagatzemar i consultar
col·leccions senceres de llibres, socis i préstecs amb col·leccions i LINQ —l'eina
que finalment permetrà deixar enrere els arrays de mida fixa i treballar amb el catàleg
complet de BiblioTech de forma còmoda i expressiva.
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
