A la lliçó anterior, cada vegada que creaves un objecte Llibre havies de recordar assignar
les seves quatre propietats a mà (Titol, Autor, Isbn, Disponible) o fer servir un
inicialitzador d'objecte; si oblidaves Disponible, el llibre naixia amb false (el valor per
defecte de bool) en lloc de true, sense que res t'avisés de l'error. Aquesta lliçó resol
aquest problema amb els constructors: un mètode especial que s'executa automàticament en
l'instant en què es crea un objecte amb new, garantint que neixi sempre en un estat
coherent. De passada, coneixeràs la seva contrapartida —els destructors— i ampliaràs el
model de domini de BiblioTech amb dues peces noves: Soci i Prestec, que junt amb Llibre
formaran el nucli sobre el qual es construirà la resta del curs.
Contingut
- Què és un constructor i quan s'executa
- El constructor per defecte
- Constructors amb paràmetres
- Sobrecàrrega de constructors
- Encadenar constructors amb
this(...) - Inicialitzadors de propietats
- Creant la classe
Soci - Unint peces: la classe
Prestec - Destructors i una menció a
IDisposable
- Què és un constructor i quan s'executa
Un constructor és un mètode especial d'una classe que s'executa automàticament cada
vegada que es crea un objecte amb new. Es distingeix d'un mètode normal en dos aspectes: té
exactament el mateix nom que la classe, i no declara cap tipus de retorn (ni tan sols
void).
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public Llibre()
{
Disponible = true;
Console.WriteLine("S'ha creat un nou llibre.");
}
}Llibre llibre1 = new Llibre(); // executa el constructor: imprimeix el missatge i posa Disponible = true
Console.WriteLine(llibre1.Disponible); // TrueTan bon punt s'executa new Llibre(), C# reserva memòria per al nou objecte i, a continuació,
executa automàticament el codi del constructor —en aquest cas, assigna Disponible = true i
mostra un missatge— abans de retornar la referència a l'objecte ja creat. Mai es crida un
constructor "a mà" com si fos un mètode qualsevol (llibre1.Llibre() seria un error de
compilació); només s'executa com a part de new.
- El constructor per defecte
Si una classe no defineix cap constructor propi, C# li proporciona automàticament un
d'implícit, sense paràmetres, que no fa res més que crear l'objecte amb totes les seves
propietats en el seu valor per defecte (com vas veure a la lliçó anterior). Això és el que ha
estat passant, sense que ho sabessis, en tots els new Llibre() de les lliçons prèvies a
aquesta.
Tan bon punt defineixes qualsevol constructor propi (com el de l'apartat anterior), aquest
constructor implícit i gratuït desapareix: si continues necessitant poder crear un
Llibre sense arguments, hauràs d'escriure tu mateix aquest constructor sense paràmetres, de
forma explícita.
- Constructors amb paràmetres
El més habitual és que un constructor rebi paràmetres per inicialitzar l'objecte amb dades concretes des del primer moment, en lloc de crear un objecte "buit" i omplir-lo després:
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public Llibre(string titol, string autor, string isbn)
{
Titol = titol;
Autor = autor;
Isbn = isbn;
Disponible = true;
}
}Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(llibre1.Titol); // Rayuela
Console.WriteLine(llibre1.Disponible); // True (es fixa sempre al constructor)Aquest constructor garanteix dues coses alhora: que no es pugui crear un Llibre sense
indicar el seu títol, autor i ISBN (són paràmetres obligatoris), i que Disponible comenci
sempre en true sense que qui crea el llibre hagi de recordar assignar-lo. Fixa't que, dins
del constructor, els noms dels paràmetres (titol, en minúscula) són diferents dels noms de
les propietats (Titol, en majúscula) precisament per poder distingir-los amb claredat en
escriure Titol = titol;.
- Sobrecàrrega de constructors
Igual que els mètodes, els constructors es poden sobrecarregar: una mateixa classe pot tenir diversos constructors, sempre que es distingeixin per la seva llista de paràmetres.
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public Llibre()
{
Titol = "Titol desconegut";
Autor = "Autor desconegut";
Isbn = "";
Disponible = true;
}
public Llibre(string titol, string autor, string isbn)
{
Titol = titol;
Autor = autor;
Isbn = isbn;
Disponible = true;
}
}Llibre llibreSenseDades = new Llibre(); // fa servir el constructor sense parametres
Llibre llibreComplet = new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1"); // fa servir el constructor amb 3 parametresC# tria automàticament quin constructor executar segons el nombre i tipus d'arguments que li
passis al new, exactament igual que amb la sobrecàrrega de mètodes vista a la lliçó anterior.
- Encadenar constructors amb
this(...)
this(...)L'exemple anterior repeteix Disponible = true; als dos constructors: si algun dia canviés
aquesta lògica d'inicialització, caldria recordar actualitzar-la als dos llocs. Per evitar
aquesta duplicació, un constructor pot cridar un altre constructor de la mateixa classe
usant this(...), delegant-hi la inicialització comuna:
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public Llibre(string titol, string autor, string isbn)
{
Titol = titol;
Autor = autor;
Isbn = isbn;
Disponible = true;
}
public Llibre() : this("Titol desconegut", "Autor desconegut", "")
{
// El cos pot quedar buit: this(...) ja ha fet la feina
}
}La sintaxi : this(...) es col·loca entre la signatura del constructor i el seu cos ({ }), i
indica "abans d'executar aquest constructor, executa primer l'altre constructor amb aquests
arguments". Així, new Llibre() acaba executant també la lògica del constructor de tres
paràmetres, amb valors per defecte per a cadascun, sense duplicar l'assignació de
Disponible. Aquesta tècnica es coneix com encadenament de constructors, i és la forma
recomanada d'evitar repetir lògica d'inicialització quan una classe té diversos constructors.
- Inicialitzadors de propietats
Per a propietats que sempre han de començar amb el mateix valor fix, independentment del constructor que s'utilitzi, C# ofereix una alternativa encara més directa: assignar el valor per defecte en la mateixa declaració de la propietat, sense necessitat de tocar cap constructor:
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; } = true; // inicialitzador de propietat
public Llibre(string titol, string autor, string isbn)
{
Titol = titol;
Autor = autor;
Isbn = isbn;
// Ja no cal assignar Disponible aqui: sempre comenca en true
}
}Un inicialitzador de propietat s'executa abans que el cos de qualsevol constructor, així que si el constructor assigna després un valor diferent a aquesta mateixa propietat, el valor del constructor preval: l'inicialitzador només actua com a valor de partida. És la forma més simple i llegible de fixar un valor per defecte quan aquest valor no depèn de cap paràmetre del constructor.
- Creant la classe
Soci
SociAmb els constructors ja dominats, és el moment d'afegir la segona peça del model de
BiblioTech: la classe Soci, que representa una persona registrada a la biblioteca amb dret a
demanar llibres en préstec.
class Soci
{
public int Id { get; set; }
public string Nom { get; set; }
public Soci(int id, string nom)
{
Id = id;
Nom = nom;
}
public void MostrarFitxa()
{
Console.WriteLine($"Soci #{Id}: {Nom}");
}
}Soci soci1 = new Soci(1, "Ana Martinez");
Soci soci2 = new Soci(2, "Luis Gomez");
soci1.MostrarFitxa(); // Soci #1: Ana Martinez
soci2.MostrarFitxa(); // Soci #2: Luis GomezSoci segueix exactament el mateix patró que Llibre: propietats autoimplementades (Id,
Nom), un constructor que exigeix les dades essencials en crear-lo, i un mètode
(MostrarFitxa()) que aprofita aquestes dades. Id identificarà cada soci de forma única
dins de BiblioTech —una idea que cobrarà més importància al Mòdul 5, en persistir dades en una
base de dades.
- Unint peces: la classe
Prestec
PrestecAmb Llibre i Soci ja definides, BiblioTech necessita representar el concepte que els
relaciona: un préstec, que vincula un llibre concret amb el soci que l'ha agafat en
préstec, junt amb les dates rellevants. Aquesta és la tercera i última peça del model de
domini del curs:
class Prestec
{
public Llibre Llibre { get; set; }
public Soci Soci { get; set; }
public DateTime DataPrestec { get; set; }
public DateTime? DataDevolucio { get; set; } // null mentre el llibre no s'ha retornat
public Prestec(Llibre llibre, Soci soci)
{
Llibre = llibre;
Soci = soci;
DataPrestec = DateTime.Now;
DataDevolucio = null;
}
public void MostrarFitxa()
{
string estat = DataDevolucio is null ? "en curs" : $"retornat el {DataDevolucio:d}";
Console.WriteLine($"Prestec de '{Llibre.Titol}' a {Soci.Nom} el {DataPrestec:d} ({estat})");
}
}Fixa't en tres detalls importants d'aquesta classe:
- Les seves propietats
LlibreiSocino són tipus primitius, sinó objectes d'altres classes que ja has definit: unPrestecno duplica el títol del llibre ni el nom del soci, sinó que guarda una referència als objectesLlibreiSocioriginals (recorda que les classes són tipus per referència). Si més endavant aquell llibre es marca com no disponible, elPrestecque el referencia "veu" automàticament el canvi, perquè apunta al mateix objecte. DataDevolucioés de tipusDateTime?(amb?): el signe d'interrogació indica un tipus que admetnull(nullable), necessari aquí perquè, mentre el préstec continua en curs, encara no existeix cap data de devolució real.- El constructor fixa
DataPrestecal moment actual (DateTime.Now) automàticament: qui crea unPrestecno ha de calcular ni passar la data a mà.
Així s'utilitzen les tres classes juntes, tal com es comportaran durant la resta del curs:
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Soci soci1 = new Soci(1, "Ana Martinez");
llibre1.Prestar(); // el llibre passa a Disponible = false (metode vist a la leccio anterior)
Prestec prestec1 = new Prestec(llibre1, soci1);
prestec1.MostrarFitxa(); // Prestec de 'Rayuela' a Ana Martinez el <data d'avui> (en curs)Amb això, el model de domini de BiblioTech queda format per tres classes que col·laboren entre
si: Llibre, Soci i Prestec. Aquest trio es mantindrà estable durant la resta del curs, i
l'aniràs enriquint (herència, encapsulació, col·leccions, persistència...) sense canviar el
seu propòsit essencial.
- Destructors i una menció a
IDisposable
IDisposableAixí com un constructor s'executa en crear un objecte, un destructor (també anomenat
finalitzador) és un mètode especial que el recol·lector d'escombraries (garbage collector)
de .NET executa, en algun moment no determinat, abans d'alliberar definitivament la memòria
d'un objecte que ja no s'utilitza. S'escriu amb el símbol ~ seguit del nom de la classe,
sense modificador d'accés ni paràmetres:
class Llibre
{
// ... propietats i constructors ...
~Llibre()
{
Console.WriteLine($"L'objecte Llibre '{Titol}' esta sent destruit.");
}
}A la pràctica, els destructors es fan servir molt poc en C# modern, per diverses raons: no
pots controlar quan s'executen exactament (depèn del recol·lector d'escombraries, no del
teu codi), afegeixen una sobrecàrrega de rendiment, i la major part de la neteja de recursos
que un destructor podria fer (tancar fitxers, connexions de xarxa o de base de dades) es
gestiona avui dia amb la interfície IDisposable i la instrucció using, que ofereixen un
control determinista i explícit sobre quan s'allibera cada recurs. IDisposable s'estudiarà
junt amb el maneig de fitxers i bases de dades al Mòdul 5; de moment n'hi ha prou que sàpigues
que existeix, i que és l'alternativa preferida enfront dels destructors per alliberar recursos
de forma fiable.
| Constructor | Destructor | |
|---|---|---|
| Símbol | Cap (mateix nom que la classe) | ~ abans del nom de la classe |
| Quan s'executa | En crear l'objecte amb new (immediat i predictible) |
En algun moment indeterminat, decidit pel recol·lector d'escombraries |
| Freqüència d'ús en C# modern | Molt habitual | Rara; es prefereix IDisposable (Mòdul 5) |
Errors Comuns i Consells
- Definir un constructor amb paràmetres i perdre el constructor sense paràmetres sense
adonar-se'n: tan bon punt afegeixes qualsevol constructor propi, el constructor per
defecte desapareix; si continues necessitant
new Llibre()sense arguments, has de declarar-lo tu mateix. - Repetir lògica d'inicialització en diversos constructors: si dos o més constructors
comparteixen part de la seva lògica, fes servir
this(...)per encadenar-los en lloc de copiar i enganxar el mateix codi a cadascun. - Confondre el nom del paràmetre amb el de la propietat:
Titol = titol;assigna a la propietat (Titol, amb majúscula) el valor del paràmetre (titol, amb minúscula); si escrivissistitol = titol;per error, no passaria res útil. - Fer servir
DateTimeen lloc deDateTime?quan el valor pot no existir encara: com queDataDevoluciopot no tenir valor mentre el préstec està en curs, ha de ser un tipus nullable (DateTime?); fer servirDateTimea seques obligaria a inventar una data "falsa" per representar "encara no retornat". - Confiar en el destructor per alliberar recursos importants: com que no es pot predir
quan s'executarà, no és una eina fiable per tancar fitxers o connexions a temps; per a això
hi ha
IDisposable(Mòdul 5).
Exercicis
-
Defineix una classe
Llibreamb les propietatsTitol,Autor,Isbn(totesstring) iDisponible(bool, inicialitzada mitjançant un inicialitzador de propietat atrue). Afegeix un únic constructorLlibre(string titol, string autor, string isbn)que assigni les tres propietats de text. Crea un objecte i comprova queDisponiblevaltruesense haver-ho assignat al constructor. -
Afegeix a la classe anterior un segon constructor sense paràmetres que faci servir
this(...)per encadenar amb el constructor de tres paràmetres, passant els valors"Titol desconegut","Autor desconegut"i"". Crea un objecte ambnew Llibre()i mostra el seuTitolper comprovar que l'encadenament funciona. -
Crea la classe
Sociamb les propietatsId(int) iNom(string) i un constructorSoci(int id, string nom). Després, crea un objecteLlibre(usant el constructor de l'exercici 1) i un objecteSoci, i utilitza tots dos per construir manualment un objectePrestec(segons la classe definida a l'apartat 8 d'aquesta lliçó), mostrant la seva fitxa ambMostrarFitxa().
Solucions
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; } = true;
public Llibre(string titol, string autor, string isbn)
{
Titol = titol;
Autor = autor;
Isbn = isbn;
}
}
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(llibre1.Disponible); // True
public Llibre() : this("Titol desconegut", "Autor desconegut", "")
{
}
Llibre llibreBuit = new Llibre();
Console.WriteLine(llibreBuit.Titol); // Titol desconegut
El constructor sense paràmetres delega en el de tres paràmetres mitjançant this(...), de
manera que Titol, Autor i Isbn acaben rebent els valors per defecte indicats, sense
duplicar codi.
class Soci
{
public int Id { get; set; }
public string Nom { get; set; }
public Soci(int id, string nom)
{
Id = id;
Nom = nom;
}
}
Llibre llibre1 = new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1");
Soci soci1 = new Soci(1, "Ana Martinez");
Prestec prestec1 = new Prestec(llibre1, soci1);
prestec1.MostrarFitxa(); // Prestec de 'Ficcions' a Ana Martinez el <data d'avui> (en curs)
Conclusió
En aquesta lliçó has après a garantir que els objectes neixin sempre en un estat coherent
mitjançant constructors: el constructor per defecte, constructors amb paràmetres, la seva
sobrecàrrega, l'encadenament amb this(...) i els inicialitzadors de propietat; també has
vist què són els destructors i per què en C# modern es prefereix IDisposable per alliberar
recursos. Amb això, el model de domini de BiblioTech queda format per tres classes que
col·laboren entre si: Llibre (títol, autor, ISBN, disponibilitat), Soci (identificador i
nom) i Prestec (quin llibre, quin soci, quan es va prestar i quan es va retornar).
Aquest trio de classes, però, té ara mateix certa duplicació oculta: si BiblioTech volgués
prestar també revistes, hauria de repetir en una nova classe Revista les mateixes propietats
(Titol, Autor, Disponible) i mètodes (Prestar(), Retornar()) que ja té Llibre. A la
propera lliçó, Herència, aprendràs a evitar aquesta duplicació extraient allò comú a una
classe base compartida, MaterialBibliotecari, de la qual Llibre (i la nova Revista)
heretaran.
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
