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

  1. Recordatori: tipus per valor enfront de tipus per referència
  2. struct: definir un tipus per valor
  3. Quan usar struct: dades petites i immutables
  4. record: igualtat per valor i impressió llegible per a classes
  5. record struct: el millor de tots dos mons
  6. Comparació completa: class, struct, record, record struct
  7. Aplicant l'elecció correcta a BiblioTech

  1. 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 canviat

Un 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 No
Exemples ja vistos Llibre, Soci, Prestec int, double, bool, DateTime

  1. struct: definir un tipus per valor

Un 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)

  1. Quan usar struct: dades petites i immutables

No 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 struct no pot heretar d'un altre struct ni 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)

  1. record: igualtat per valor i impressió llegible per a classes

A 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.

record ResumPrestec(string TitolLlibre, string NomSoci, DateTime DataPrestec);

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 memoria

Compara 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.

  1. record struct: el millor de tots dos mons

El 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:

record struct Adreca(string Carrer, string Ciutat, string CodiPostal);

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

  1. Comparació completa: class, struct, record, record struct

class 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)
Suporta herència de classes 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

  1. 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 un Soci, on interessa tant el comportament de còpia per valor com la igualtat i el ToString() 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 struct per a una entitat amb identitat pròpia: si un tipus necessita comportament ric, mutabilitat lliure o participar en una jerarquia d'herència (com Llibre o Prestec), ha de ser una class, no un struct; forçar un struct aquí generaria còpies inesperades de dades que haurien de compartir-se.
  • Sorprendre's perquè una còpia d'un struct no 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 una class, no un struct.
  • Confondre record amb record struct: un record a seques continua sent un tipus per referència (guanya igualtat per valor i ToString(), però no el comportament de còpia); si a més vols el comportament de còpia per valor, necessites record struct.
  • Definir structs molt grans: si un struct acumula molts camps, copiar-lo deixa de ser barat i pot perjudicar el rendiment; en aquest cas, una class (o repensar el disseny) sol ser una millor opció.
  • Consell: davant el dubte entre class i struct/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

  1. Defineix un struct RangDates amb les propietats Inici i Fi (totes dues DateTime), un constructor que les rebi, i un mètode int DuracioEnDies() que retorni la diferència en dies entre totes dues dates. Crea dos rangs de dates diferents i mostra la seva durada.

  2. 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 el record struct i comprova amb == que es consideren iguals, i mostra'n una directament amb Console.WriteLine per comprovar el ToString() automàtic.

  3. Defineix record ResumPrestec(string TitolLlibre, string NomSoci, DateTime DataPrestec);. Crea dos objectes ResumPrestec amb exactament els mateixos valors però creats per separat amb new, 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#

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