A la lliçó anterior vas definir la classe Llibre amb les seves propietats (Titol, Autor,
Isbn, Disponible), però un objecte Llibre era, de moment, un contenidor passiu de dades:
per prestar-lo calia escriure codi extern que posés Disponible a false a mà, i per mostrar
la seva fitxa calia escriure el Console.WriteLine corresponent cada vegada, en qualsevol part
del programa que ho necessités. Aquesta lliçó resol això ensenyant-te a definir mètodes:
blocs de codi associats a una classe que representen les accions que els seus objectes poden
realitzar. Al final de la lliçó, un objecte Llibre sabrà prestar-se, retornar-se i mostrar la
seva pròpia fitxa, sense que el codi que l'utilitza hagi de conèixer els detalls interns de com
ho fa.
Contingut
- Què és un mètode i per què viu dins de la classe
- Sintaxi d'un mètode: paràmetres i tipus de retorn
- Mètodes
void: accions sense valor de retorn - Afegint mètodes a
Llibre:Prestar(),Retornar(),MostrarFitxa() - Sobrecàrrega de mètodes
- Pas de paràmetres per valor
- Pas de paràmetres per referència:
refiout - Paràmetres opcionals i arguments amb nom
- Mètodes d'instància enfront de mètodes
static
- Què és un mètode i per què viu dins de la classe
Un mètode és una funció definida dins d'una classe que descriu una acció que els seus
objectes poden executar. La diferència amb les funcions locals que ja vas fer servir al Mòdul 2
(com PrestarLlibre(string titol), que rebia el títol com a paràmetre) és que un mètode viu
dins de la classe i, normalment, opera directament sobre les propietats del propi objecte,
sense necessitat que se li passin com a 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 void Prestar()
{
Disponible = false;
}
}Fixa't en la diferència d'enfocament: al Mòdul 2, PrestarLlibre(titolsLlibres, disponibilitat, posicio) necessitava que li passessis tot l'array de disponibilitat com a paràmetre. Ara,
llibre1.Prestar() no necessita cap paràmetre: el mètode ja sap a quin objecte pertany (a
llibre1) i accedeix directament a la seva propietat Disponible. Aquesta capacitat d'un
mètode d'accedir a les propietats de l'objecte sobre el qual s'invoca és la base de la
programació orientada a objectes.
- Sintaxi d'un mètode: paràmetres i tipus de retorn
La forma general d'un mètode és:
- El modificador d'accés (
public,private...) controla des d'on es pot cridar al mètode; s'estudia en profunditat a la lliçó d'Encapsulació. De moment, farem servirpublicperquè sigui accessible des de qualsevol part del programa. - El tipus de retorn indica quin tipus de dada retorna el mètode en acabar (
int,string,bool...), ovoidsi no retorna cap valor. - Els paràmetres, entre parèntesis, són les dades que el mètode necessita rebre per fer la seva feina (poden ser zero, un o diversos).
Aquest mètode retorna un bool: true si l'Autor del llibre coincideix amb el paràmetre
autorCercat, false en cas contrari. La paraula clau return acaba l'execució del mètode i
lliura el valor indicat a qui l'ha cridat.
Llibre llibre1 = new Llibre { Titol = "Rayuela", Autor = "Julio Cortazar" };
bool esDeCortazar = llibre1.TeAutor("Julio Cortazar");
Console.WriteLine(esDeCortazar); // True
- Mètodes
void: accions sense valor de retorn
void: accions sense valor de retornQuan un mètode realitza una acció però no necessita retornar cap dada, el seu tipus de retorn
és void (literalment, "buit"). Ja vas fer servir void a les funcions locals del Mòdul 2; en
un mètode de classe es comporta igual:
public void MostrarFitxa()
{
Console.WriteLine($"Titol: {Titol}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {Isbn}");
Console.WriteLine($"Disponible: {Disponible}");
}Un mètode void pot fer servir return; (sense cap valor darrere) per acabar abans d'hora si
cal, però no pot fer servir return valor;, ja que no té cap tipus al qual retornar aquest
valor.
- Afegint mètodes a
Llibre: Prestar(), Retornar(), MostrarFitxa()
Llibre: Prestar(), Retornar(), MostrarFitxa()Amb el que s'ha vist fins ara, ja pots completar la classe Llibre amb els seus tres mètodes
principals, que acompanyaran l'objecte durant la resta del mòdul:
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public void Prestar()
{
if (Disponible)
{
Disponible = false;
Console.WriteLine($"'{Titol}' ha estat prestat.");
}
else
{
Console.WriteLine($"'{Titol}' no esta disponible per a prestec.");
}
}
public void Retornar()
{
Disponible = true;
Console.WriteLine($"'{Titol}' ha estat retornat.");
}
public void MostrarFitxa()
{
Console.WriteLine($"Titol: {Titol}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {Isbn}");
Console.WriteLine($"Disponible: {Disponible}");
}
}I així s'utilitzen sobre un objecte concret:
Llibre llibre1 = new Llibre { Titol = "Cien anos de soledad", Autor = "Gabriel Garcia Marquez", Isbn = "978-84-376-0494-7", Disponible = true };
llibre1.MostrarFitxa();
llibre1.Prestar(); // 'Cien anos de soledad' ha estat prestat.
llibre1.Prestar(); // 'Cien anos de soledad' no esta disponible per a prestec.
llibre1.Retornar(); // 'Cien anos de soledad' ha estat retornat.Observa la diferència amb el Mòdul 2: llibre1.Prestar() no necessita que li passis ni el
títol, ni l'array de disponibilitat, ni cap posició; el mateix objecte ja coneix les seves
dades i decideix, segons el seu estat intern (Disponible), què fer. Aquesta és la millora
concreta que aporta l'orientació a objectes respecte a les variables soltes del Mòdul 2.
- Sobrecàrrega de mètodes
La sobrecàrrega (overloading) consisteix a definir diversos mètodes amb el mateix nom però diferent llista de paràmetres (diferent nombre de paràmetres, o diferent tipus). C# decideix automàticament quina de les versions executar segons els arguments amb què se'l cridi.
public void MostrarFitxa()
{
Console.WriteLine($"Titol: {Titol}");
Console.WriteLine($"Autor: {Autor}");
Console.WriteLine($"ISBN: {Isbn}");
Console.WriteLine($"Disponible: {Disponible}");
}
public void MostrarFitxa(bool incloureIsbn)
{
Console.WriteLine($"Titol: {Titol}");
Console.WriteLine($"Autor: {Autor}");
if (incloureIsbn)
{
Console.WriteLine($"ISBN: {Isbn}");
}
Console.WriteLine($"Disponible: {Disponible}");
}llibre1.MostrarFitxa(); // fa servir la versio sense parametres
llibre1.MostrarFitxa(false); // fa servir la versio amb un bool, sense mostrar l'ISBNC# distingeix aquestes dues versions únicament per la seva signatura (nom + nombre i tipus de paràmetres), no pel tipus de retorn ni pels noms dels paràmetres. Dos mètodes amb el mateix nom i exactament els mateixos paràmetres —encara que tinguessin tipus de retorn diferents— no serien una sobrecàrrega vàlida i el compilador ho rebutjaria.
| Mètode | Paràmetres | Quan s'utilitza? |
|---|---|---|
MostrarFitxa() |
Cap | Fitxa completa per defecte |
MostrarFitxa(bool incloureIsbn) |
Un bool |
Es pot decidir si mostrar o no l'ISBN |
- Pas de paràmetres per valor
Per defecte, en C# els paràmetres es passen per valor: el mètode rep una còpia del
valor que se li passa, no l'original. Si el paràmetre és d'un tipus primitiu (int, bool,
double...), modificar el paràmetre dins del mètode no afecta la variable original que es va
passar en cridar-lo.
void Duplicar(int nombre)
{
nombre = nombre * 2;
Console.WriteLine($"Dins del metode: {nombre}");
}
int copiesDisponibles = 5;
Duplicar(copiesDisponibles);
Console.WriteLine($"Fora del metode: {copiesDisponibles}"); // segueix sent 5Encara que dins de Duplicar la variable local nombre acaba valent 10, la variable
original copiesDisponibles, fora del mètode, no es veu afectada: nombre era només una
còpia.
Nota: això és diferent del que vas veure a la lliçó anterior amb objectes com
Llibre. Un paràmetre de tipusLlibretambé es passa "per valor", però el que es copia és la referència a l'objecte, no l'objecte en si; per això, si un mètode rep unLlibrecom a paràmetre i modifica una de les seves propietats (llibre.Disponible = false;), aquest canvi sí es reflecteix fora del mètode, perquè la còpia de la referència segueix apuntant al mateix objecte. Tornarem sobre aquesta diferència entre tipus per valor i per referència a l'última lliçó del mòdul.
- Pas de paràmetres per referència:
ref i out
ref i outQuan sí necessites que un mètode modifiqui directament la variable original (no una còpia), C#
ofereix dues paraules clau: ref i out.
ref: modificar una variable ja inicialitzada
ref indica que el paràmetre es passa per referència: el mètode rep accés directe a la
variable original, no a una còpia, així que qualsevol canvi dins del mètode es reflecteix fora.
La variable ha d'estar inicialitzada abans de la crida, i cal repetir ref tant en declarar el
mètode com en cridar-lo.
void DuplicarExemplars(ref int quantitatExemplars)
{
quantitatExemplars = quantitatExemplars * 2;
}
int exemplarsRayuela = 3;
DuplicarExemplars(ref exemplarsRayuela);
Console.WriteLine(exemplarsRayuela); // 6 -- aquesta vegada SI ha canviatout: un mètode que "retorna" diversos valors
out s'utilitza perquè un mètode pugui lliurar un valor addicional a més de (o en lloc de) el
seu return, típicament per indicar si una operació ha tingut èxit juntament amb un missatge o
resultat. A diferència de ref, la variable passada com a out no necessita estar
inicialitzada abans de la crida —el mètode està obligat a assignar-li un valor abans
d'acabar. Ja vas fer servir aquest patró, sense saber-ho encara, amb int.TryParse(entrada, out int numero) al Mòdul 1.
bool IntentarPrestar(Llibre llibre, out string missatge)
{
if (llibre.Disponible)
{
llibre.Disponible = false;
missatge = $"'{llibre.Titol}' prestat correctament.";
return true;
}
missatge = $"'{llibre.Titol}' no esta disponible.";
return false;
}
Llibre llibre2 = new Llibre { Titol = "Ficcions", Disponible = true };
bool exit = IntentarPrestar(llibre2, out string resultat);
Console.WriteLine(resultat); // 'Ficcions' prestat correctament.
Console.WriteLine(exit); // Trueref |
out |
|
|---|---|---|
| La variable ha d'estar inicialitzada abans de cridar | Sí | No |
| El mètode està obligat a assignar-li un valor | No | Sí |
| Ús típic | Modificar un valor existent | Retornar un resultat addicional (sovint junt amb un bool d'èxit) |
- Paràmetres opcionals i arguments amb nom
Un paràmetre opcional té un valor per defecte assignat en la mateixa definició del mètode; si qui crida el mètode no proporciona aquest argument, s'utilitza el valor per defecte. Els paràmetres opcionals han d'anar sempre després dels paràmetres obligatoris.
public void RegistrarExemplar(string titol, string autor, string estat = "Nou")
{
Console.WriteLine($"Registrat: {titol} ({autor}) - Estat: {estat}");
}llibre1.RegistrarExemplar("Rayuela", "Julio Cortazar"); // fa servir "Nou" per defecte
llibre1.RegistrarExemplar("Rayuela", "Julio Cortazar", "Usat"); // sobreescriu el valor per defecteEls arguments amb nom permeten indicar explícitament a quin paràmetre correspon cada valor,
escrivint nomParametre: valor, en lloc de dependre únicament de l'ordre. Això és especialment
útil quan un mètode té diversos paràmetres opcionals i només vols especificar-ne algun:
llibre1.RegistrarExemplar(titol: "Ficcions", autor: "Jorge Luis Borges", estat: "Usat");
// Amb arguments amb nom, l'ordre deixa d'importar:
llibre1.RegistrarExemplar(autor: "Jorge Luis Borges", titol: "Ficcions");A la segona crida, encara que autor s'escriu abans que titol, C# assigna cada valor al
paràmetre correcte gràcies al nom explícit, i estat pren el seu valor per defecte
("Nou") en no indicar-se.
- Mètodes d'instància enfront de mètodes
static
staticTots els mètodes vistos fins ara són mètodes d'instància: només té sentit cridar-los sobre
un objecte concret (llibre1.Prestar()), perquè operen sobre les dades d'aquest objecte en
particular. Un mètode static, en canvi, pertany a la pròpia classe, no a cap objecte
concret, i s'invoca escrivint el nom de la classe en lloc del d'una variable:
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public static bool EsIsbnValid(string isbn)
{
string isbnNet = isbn.Replace("-", "");
return isbnNet.Length == 13 && isbnNet.StartsWith("978") || isbnNet.StartsWith("979");
}
// Prestar(), Retornar(), MostrarFitxa() es mantenen com a l'apartat 4
}Fixa't que EsIsbnValid s'invoca com Llibre.EsIsbnValid(...), usant el nom de la classe,
no sobre un objecte (llibre1.EsIsbnValid(...) seria un error de compilació). Això té sentit
perquè validar el format d'un ISBN no depèn de cap llibre concret: és una utilitat general
relacionada amb el concepte Llibre, però que no necessita cap objecte per executar-se. Per
aquesta mateixa raó, un mètode static no pot accedir directament a propietats d'instància
com Titol o Disponible (no sabria de quin objecte parlar); rebria un error de compilació si
ho intentés.
| Mètode d'instància | Mètode static |
|
|---|---|---|
| S'invoca sobre | Un objecte (llibre1.Prestar()) |
La classe (Llibre.EsIsbnValid(...)) |
Accedeix a propietats de l'objecte (Titol, Disponible...) |
Sí | No, directament |
Exemple a Llibre |
Prestar(), Retornar(), MostrarFitxa() |
EsIsbnValid(string isbn) |
| Quan usar-lo | L'operació depèn de les dades d'un objecte concret | L'operació és una utilitat general, no lligada a cap objecte |
Console.WriteLine i int.Parse, que ja has fet servir en mòduls anteriors, són també mètodes
static (de les classes Console i int respectivament): per això es criden amb el nom de la
classe (Console, no una variable), i no sobre cap objecte.
Errors Comuns i Consells
- Oblidar
ref/outen cridar el mètode: si el mètode es va declarar ambrefoout, cal repetir la paraula clau també en la crida (DuplicarExemplars(ref exemplarsRayuela)), no només en la definició; si no, el compilador dona un error. - Fer servir una variable
outsense inicialitzar-la i esperar llegir-la abans de la crida: una variable declarada comout string missatge(inline, en la mateixa crida) no té cap valor útil fins que el mètode acaba d'executar-se; no intentis llegir-la abans. - Confondre sobrecàrrega amb paràmetres opcionals: totes dues permeten "cridar el mètode de diverses formes", però són mecanismes diferents: la sobrecàrrega defineix mètodes completament separats (amb cossos potencialment diferents), mentre que un paràmetre opcional forma part d'un únic mètode amb un valor per defecte.
- Intentar accedir a
TitoloDisponibledes d'un mètodestatic: un mètodestaticno està associat a cap objecte, així que no pot usar directament propietats d'instància; si necessita dades d'un llibre concret, aquest llibre li ha d'arribar com a paràmetre. - Abusar de
ref/out: són útils en casos concrets (comTryParse/IntentarPrestar), però un mètode que retorna el seu resultat ambreturnsol ser més clar que un que ho fa mitjançant paràmetresref/out; reserva'ls per quan de veritat aportin alguna cosa, no com a costum.
Exercicis
-
Afegeix a la classe
Llibre(amb les propietatsTitol,Autor,Isbn,Disponible) un mètode d'instànciabool EsDelAutor(string autorCercat)que retornitruesi l'Autordel llibre coincideix exactament ambautorCercat, ifalseen cas contrari. Prova'l amb un objecteLlibrei dues cerques diferents. -
Escriu un mètode
staticbool EsIsbnValid(string isbn)dins de la classeLlibreque retornitruesi, després de treure els guions ambReplace("-", ""), la cadena resultant té exactament 13 caràcters, ifalseen cas contrari. Crida'l ambLlibre.EsIsbnValid(...)fent servir dos ISBN d'exemple (un vàlid i un altre no). -
Escriu un mètode
void RegistrarPrestec(string nomSoci, int diesPrestec = 15)que mostri per consola un missatge amb el nom del soci i els dies de préstec. Crida'l tres vegades: una sense indicardiesPrestec(ha de fer servir 15 per defecte), una altra indicant 30 dies per posició, i una altra fent servir arguments amb nom en ordre invertit.
Solucions
class Llibre
{
public string Titol { get; set; }
public string Autor { get; set; }
public string Isbn { get; set; }
public bool Disponible { get; set; }
public bool EsDelAutor(string autorCercat)
{
return Autor == autorCercat;
}
}
Llibre llibre1 = new Llibre { Titol = "Rayuela", Autor = "Julio Cortazar" };
Console.WriteLine(llibre1.EsDelAutor("Julio Cortazar")); // True
Console.WriteLine(llibre1.EsDelAutor("Jorge Luis Borges")); // False
public static bool EsIsbnValid(string isbn)
{
string isbnNet = isbn.Replace("-", "");
return isbnNet.Length == 13;
}
Console.WriteLine(Llibre.EsIsbnValid("978-84-376-0494-7")); // True (13 caracters sense guions)
Console.WriteLine(Llibre.EsIsbnValid("123")); // False
void RegistrarPrestec(string nomSoci, int diesPrestec = 15)
{
Console.WriteLine($"{nomSoci}: prestec de {diesPrestec} dies.");
}
RegistrarPrestec("Ana Martinez"); // fa servir 15 dies per defecte
RegistrarPrestec("Luis Gomez", 30); // 30 dies per posicio
RegistrarPrestec(diesPrestec: 7, nomSoci: "Eva Ruiz"); // arguments amb nom, ordre invertit
Les tres crides són vàlides: la primera fa servir el valor per defecte de diesPrestec, la
segona l'indica per posició, i la tercera fa servir arguments amb nom, cosa que permet
escriure'ls en qualsevol ordre.
Conclusió
En aquesta lliçó, Llibre ha passat de ser un simple contenidor de dades a un objecte amb
comportament propi: sap prestar-se (Prestar()), retornar-se (Retornar()) i mostrar la seva
fitxa (MostrarFitxa()), i has après les eines fonamentals per definir mètodes en C#:
paràmetres i retorn, void, sobrecàrrega, pas per valor enfront de ref/out, paràmetres
opcionals i amb nom, i la diferència entre mètodes d'instància i mètodes static.
Encara queda una pregunta pendent: com s'assegura que un objecte Llibre neix sempre amb les
seves propietats ben inicialitzades (per exemple, Disponible = true en crear un llibre nou),
en lloc de confiar que qui el crea recordi assignar cada propietat a mà? La resposta són els
constructors, que veuràs a la propera lliçó juntament amb la nova classe Soci: el segon
pilar del model de domini de BiblioTech.
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
