Fins ara, tot el codi de BiblioTech ha conegut en temps de compilació quines propietats i mètodes té cada classe: en escriure llibre1.Titol, el compilador comprova que Llibre té una propietat Titol abans de generar el programa. La reflexió (System.Reflection) inverteix aquesta idea: permet que un programa inspeccioni i manipuli els seus propis tipus en temps d'execució, sense conèixer-los d'antuvi. És, literalment, el mecanisme que fa possible que JsonSerializer (Mòdul 5) sàpiga quines propietats té un Llibre sense que ningú hagi escrit codi específic de mapeig per a aquesta classe: per sota, recorre les seves propietats per reflexió. Aquesta lliçó obre el Mòdul 6 explicant com funciona aquest mecanisme i com fer-lo servir directament.

Contingut

  1. Què és la reflexió i per a què serveix
  2. Obtenir un Type: typeof i GetType()
  3. Inspeccionar propietats i mètodes: GetProperties() i GetMethods()
  4. Llegir i escriure valors de propietats per reflexió
  5. Crear instàncies dinàmicament amb Activator.CreateInstance
  6. MostrarPropietats: un inspector genèric per al domini de BiblioTech
  7. Cost de la reflexió i casos d'ús reals

  1. Què és la reflexió i per a què serveix

La reflexió és la capacitat d'un programa d'examinar la seva pròpia estructura —classes, propietats, mètodes, atributs— com si fossin dades, i d'actuar-hi sense que el codi que la fa servir conegui d'antuvi el tipus concret amb què treballa. Tot el namespace System.Reflection gira al voltant d'un tipus central: System.Type, que representa, en temps d'execució, "la descripció completa d'una classe" (les seves propietats, els seus mètodes, la seva classe base, les seves interfícies...).

La reflexió no és una eina que es faci servir cada dia en el codi de domini d'una aplicació com BiblioTech (Llibre.Titol es continua escrivint directament, sense reflexió, en el 99% del codi). El seu valor apareix en codi genèric i d'infraestructura: frameworks que necessiten treballar amb "qualsevol tipus" sense conèixer-lo per endavant. Ja has fet servir diverses peces construïdes així sense saber-ho:

Eina ja vista al curs Què fa per sota amb reflexió
JsonSerializer.Serialize/Deserialize (Mòdul 5) Recorre les propietats públiques d'un tipus per llegir-les o assignar-les
Entity Framework Core (DbSet<T>) Inspecciona les propietats de Llibre, Soci... per mapejar-les a columnes
Frameworks d'injecció de dependències (Mòdul 8) Creen instàncies de classes el tipus concret de les quals no coneixen en temps de compilació

  1. Obtenir un Type: typeof i GetType()

Hi ha dues maneres d'obtenir el Type que representa una classe, segons si el tipus es coneix en temps de compilació o només es disposa d'una instància en temps d'execució:

using System;

// Forma 1: typeof, quan el tipus es coneix en temps de compilacio
Type tipusLlibre = typeof(Llibre);

// Forma 2: GetType(), quan nomes es te una instancia (el tipus pot no coneixer-se d'antuvi)
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Type tipusDesDeInstancia = llibre1.GetType();

Console.WriteLine(tipusLlibre == tipusDesDeInstancia); // True: tots dos representen el mateix Type
Console.WriteLine(tipusLlibre.Name);                    // "Llibre"
Console.WriteLine(tipusLlibre.FullName);                // el nom complet, amb namespace
typeof(T) objecte.GetType()
Quan es fa servir El tipus es coneix al codi, escrit literalment Només es disposa d'una referència en temps d'execució
Resultat amb polimorfisme El tipus exacte indicat entre parèntesis El tipus real de l'objecte, encara que la variable sigui d'un tipus més general

El segon matís és important: si MaterialBibliotecari material = llibre1; (recordant el Polimorfisme del Mòdul 3), material.GetType() retorna Llibre, no MaterialBibliotecariGetType() sempre revela el tipus concret de l'objecte en memòria, sense importar el tipus de la variable que el referencia.

  1. Inspeccionar propietats i mètodes: GetProperties() i GetMethods()

Un Type exposa, entre molts altres, els mètodes GetProperties() i GetMethods(), que retornen arrays de PropertyInfo i MethodInfo respectivament: objectes que descriuen, un per un, cada propietat o mètode públic del tipus.

using System.Reflection;

Type tipusLlibre = typeof(Llibre);

PropertyInfo[] propietats = tipusLlibre.GetProperties();
foreach (PropertyInfo propietat in propietats)
{
    Console.WriteLine($"Propietat: {propietat.Name} (tipus {propietat.PropertyType.Name})");
}
// Propietat: Titol (tipus String)
// Propietat: Autor (tipus String)
// Propietat: Disponible (tipus Boolean)
// Propietat: Isbn (tipus String)

MethodInfo[] metodes = tipusLlibre.GetMethods();
foreach (MethodInfo metode in metodes)
{
    Console.WriteLine($"Metode: {metode.Name}");
}
// Metode: Prestar, Retornar, MostrarFitxa, Descriure, Coincideix, GetType, ToString... (heretats inclosos)

GetProperties() sobre typeof(Llibre) inclou tant les propietats declarades directament a Llibre (Isbn) com les heretades de MaterialBibliotecari (Titol, Autor, Disponible): la reflexió respecta la jerarquia d'herència igual que el propi llenguatge. GetMethods(), per defecte, també inclou mètodes heretats d'object (ToString(), GetType(), Equals()...), cosa que convé tenir present en recórrer el resultat.

  1. Llegir i escriure valors de propietats per reflexió

Un PropertyInfo no és només una descripció: també permet llegir (GetValue) o escriure (SetValue) el valor d'aquesta propietat sobre una instància concreta, passada com a argument:

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

PropertyInfo? propietatTitol = typeof(Llibre).GetProperty("Titol");
object? valorTitol = propietatTitol?.GetValue(llibre1);
Console.WriteLine(valorTitol); // "Rayuela"

propietatTitol?.SetValue(llibre1, "Rayuela (edicio revisada)");
Console.WriteLine(llibre1.Titol); // "Rayuela (edicio revisada)"

GetProperty("Titol") cerca, pel seu nom com a cadena de text, la propietat concreta Titol; retorna null si no existeix cap amb aquest nom exacte, d'aquí el ? a GetValue. Això és justament el que distingeix la reflexió d'accedir a llibre1.Titol directament: el nom de la propietat pot ser una dada (una cadena que arriba des de configuració, des d'un fitxer, o que es calcula en un bucle), no un identificador fix escrit al codi.

Disponible, amb el seu private set (Mòdul 3), continua respectant el seu encapsulament davant SetValue per defecte: GetProperty("Disponible")?.SetValue(llibre1, false) llança una excepció, tret que se sol·liciti explícitament accés a membres no públics amb BindingFlags.NonPublic, una possibilitat que existeix però que convé evitar —saltar-se un private set per reflexió trenca precisament la garantia que aquest private set pretenia donar.

  1. Crear instàncies dinàmicament amb Activator.CreateInstance

A més d'inspeccionar tipus ja instanciats, la reflexió permet crear instàncies noves d'un tipus que només es coneix en temps d'execució (per exemple, a partir del seu nom com a cadena de text), amb Activator.CreateInstance:

using System;

Type tipusSoci = typeof(Soci);

// Activator.CreateInstance necessita els arguments exactes d'algun constructor de Soci
object? nouSoci = Activator.CreateInstance(tipusSoci, 99, "Soci Creat Dinamicament");

if (nouSoci is Soci soci)
{
    Console.WriteLine($"{soci.Id}: {soci.Nom}"); // 99: Soci Creat Dinamicament
}

Activator.CreateInstance(tipus, arguments...) cerca, entre els constructors públics de tipus, un la signatura del qual coincideixi amb els arguments indicats, i l'invoca; el resultat es retorna com a object, per la qual cosa cal un is/as (Mòdul 3) per tornar-lo a tractar com a Soci amb tots els seus membres. Aquest mecanisme és la base de molts frameworks: un contenidor d'injecció de dependències (esmentat de passada aquí; es retoma al Mòdul 8) rep, en temps d'execució, una llista de tipus a instanciar sense conèixer-los en temps de compilació, i fa servir Activator.CreateInstance (o mecanismes equivalents més optimitzats) per construir-los.

  1. MostrarPropietats: un inspector genèric per al domini de BiblioTech

Unint GetType(), GetProperties() i GetValue(), es pot escriure una única funció que imprimeix el nom i el valor de cada propietat pública de qualsevol objecte, sense necessitat d'un mètode MostrarFitxa() diferent per a cada classe del domini:

using System.Reflection;

static void MostrarPropietats(object obj)
{
    Type tipus = obj.GetType();
    Console.WriteLine($"--- {tipus.Name} ---");

    foreach (PropertyInfo propietat in tipus.GetProperties())
    {
        object? valor = propietat.GetValue(obj);
        Console.WriteLine($"{propietat.Name}: {valor}");
    }
}
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Soci soci1 = new Soci(1, "Ana Martinez");

MostrarPropietats(llibre1);
// --- Llibre ---
// Titol: Rayuela
// Autor: Julio Cortazar
// Disponible: True
// Isbn: 978-84-376-0495-4

MostrarPropietats(soci1);
// --- Soci ---
// Id: 1
// Nom: Ana Martinez

MostrarPropietats rep object obj (el tipus més general possible, Mòdul 3) precisament perquè no necessita saber d'antuvi si rebrà un Llibre, una Revista o un Soci: fa servir obj.GetType() per descobrir-ho en temps d'execució, i a partir d'aquí recorre les seves propietats de forma genèrica. És una eina de depuració molt pràctica: un únic mètode serveix per inspeccionar l'estat intern de qualsevol objecte del domini, sense escriure ni mantenir un MostrarFitxa() a mida per cada classe nova que s'afegeixi a BiblioTech.

flowchart LR
    A["MostrarPropietats(obj)"] --> B["obj.GetType()"]
    B --> C["tipus.GetProperties()"]
    C --> D["Per cada PropertyInfo: propietat.GetValue(obj)"]
    D --> E["Console.WriteLine(nom + valor)"]

  1. Cost de la reflexió i casos d'ús reals

La reflexió té un cost de rendiment notable davant l'accés directe (llibre1.Titol): localitzar un membre pel seu nom, comprovar tipus i signatures en temps d'execució és sensiblement més lent que una crida resolta en temps de compilació. Això no vol dir que s'hagi d'evitar sempre, sinó que cal reservar-la per a on de veritat aporta valor:

Cas d'ús Per què encaixa amb reflexió
Serialitzadors genèrics (JsonSerializer) Necessiten funcionar amb qualsevol classe, sense codi específic per tipus
Frameworks d'injecció de dependències (Mòdul 8) Creen instàncies de tipus configurats externament, no coneguts en temps de compilació
Eines de depuració/inspecció (MostrarPropietats) El benefici de la inspecció genèrica compensa el cost, perquè no s'executen al camí crític de rendiment
Codi de domini quotidià (llibre1.Prestar()) No encaixa: l'accés directe és més ràpid, més segur en temps de compilació i més llegible

Errors Comuns i Consells

  • Fer servir reflexió on l'accés directe ja funciona: si el tipus es coneix en temps de compilació (el cas habitual en el codi de domini de BiblioTech), accedir directament a llibre1.Titol és més ràpid, més segur i més llegible que cercar-lo per reflexió amb una cadena de text.
  • Oblidar comprovar null a GetProperty/GetMethod: si el nom cercat no existeix (per exemple, per una errata a la cadena de text), aquests mètodes retornen null en lloc de llançar una excepció; accedir a .GetValue(...) sobre aquest null sense comprovar-ho llança una NullReferenceException.
  • Saltar-se l'encapsulament amb BindingFlags.NonPublic sense necessitat real: és tècnicament possible llegir o escriure membres private per reflexió, però fer-ho rutinàriament trenca les garanties de disseny (com el private set de Disponible) que el propi codi va establir a propòsit.
  • No mesurar l'impacte en rendiment abans de fer servir reflexió en un camí crític: en un bucle que s'executa milions de vegades, la diferència entre accés directe i reflexió pot ser significativa; per a codi d'infraestructura molt sensible al rendiment existeixen alternatives més avançades (compilació d'expressions, Source Generators) fora de l'abast d'aquest curs.
  • Consell: abans d'escriure codi amb reflexió, pregunta't si el problema es pot resoldre amb les eines ja vistes (interfícies, genèrics, polimorfisme); la reflexió és l'eina correcta quan el tipus concret no es coneix fins a temps d'execució, no un substitut general d'un bon disseny orientat a objectes.

Exercicis

  1. Fent servir typeof i GetType(), comprova que typeof(Revista) i new Revista("Muy Interesante", "Redaccion", 350).GetType() representen el mateix Type. Imprimeix també tipus.Name i tipus.BaseType?.Name per a Revista (pista: BaseType retorna el Type de la classe base, aquí MaterialBibliotecari).

  2. Escriu la funció MostrarPropietats(object obj) d'aquesta lliçó i utilitza-la per mostrar les propietats d'un Llibre, una Revista i un Soci diferents.

  3. Escriu una funció bool TePropietat(object obj, string nomPropietat) que retorni true si el tipus d'obj té una propietat pública amb aquest nom exacte (fent servir GetProperty i comprovant si el resultat és diferent de null), i false en cas contrari. Prova-la amb "Titol" (ha de donar true sobre un Llibre) i amb "Preu" (ha de donar false).

Solucions

Type tipusPerTypeof = typeof(Revista);
Revista revista1 = new Revista("Muy Interesante", "Redaccion", 350);
Type tipusPerInstancia = revista1.GetType();

Console.WriteLine(tipusPerTypeof == tipusPerInstancia); // True
Console.WriteLine(tipusPerTypeof.Name);                  // "Revista"
Console.WriteLine(tipusPerTypeof.BaseType?.Name);        // "MaterialBibliotecari"
static void MostrarPropietats(object obj)
{
    Type tipus = obj.GetType();
    Console.WriteLine($"--- {tipus.Name} ---");

    foreach (PropertyInfo propietat in tipus.GetProperties())
    {
        Console.WriteLine($"{propietat.Name}: {propietat.GetValue(obj)}");
    }
}

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Revista revista1 = new Revista("Muy Interesante", "Redaccion", 350);
Soci soci1 = new Soci(1, "Ana Martinez");

MostrarPropietats(llibre1);
MostrarPropietats(revista1);
MostrarPropietats(soci1);
static bool TePropietat(object obj, string nomPropietat)
{
    return obj.GetType().GetProperty(nomPropietat) != null;
}

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

Console.WriteLine(TePropietat(llibre1, "Titol")); // True
Console.WriteLine(TePropietat(llibre1, "Preu"));   // False

Conclusió

En aquesta lliçó has conegut la reflexió: com obtenir el Type d'una classe amb typeof o GetType(), com inspeccionar les seves propietats i mètodes amb GetProperties()/GetMethods(), com llegir i escriure valors per reflexió amb GetValue()/SetValue(), i com crear instàncies dinàmicament amb Activator.CreateInstance. També has vist que la reflexió és precisament el mecanisme que fa possibles eines ja fetes servir al curs, com JsonSerializer o Entity Framework Core, i per què convé reservar-la per a codi genèric d'infraestructura en lloc del dia a dia del domini.

La lliçó següent, Atributs, retoma alguna cosa que ja has vist de passada sense aturar-t'hi: [JsonPropertyName] i [JsonPolymorphic] de la lliçó de Serialització són, en realitat, atributs, una manera d'anotar codi amb metadades addicionals. I la reflexió que acabes d'aprendre és exactament l'eina que permet llegir aquests atributs en temps d'execució per prendre decisions —la peça que faltava per entendre com funcionen de veritat per sota.

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