Tot el codi de BiblioTech escrit fins ara és fortament tipat: el compilador coneix, a cada
línia, el tipus exacte de cada variable (Llibre, string, List<Soci>...) i rebutja
qualsevol operació que no encaixi —accedir a una propietat inexistent, passar un argument del
tipus equivocat— abans que el programa arribi a executar-se. C#, a més, ofereix un tipus especial
que renuncia deliberadament a aquesta comprovació: dynamic. Aquesta lliçó explica què és, en
què es diferencia d'object i de var (amb els quals es confon sovint), quan té sentit fer-lo
servir, i per què el domini de BiblioTech, tipat de forma conscient des del Mòdul 1, ha de
continuar evitant-lo en el seu codi de negoci.
Contingut
- El tipus
dynamic: resolució en temps d'execució dynamicdavantobject: la mateixa dada, comprovació en moments diferentsdynamicdavantvar: dinàmic no és el mateix que inferitExpandoObject: objectes dinàmics sense una classe definida- Quan té sentit fer servir
dynamic - Per què evitar
dynamicen el domini de BiblioTech - Exemple: llegir un JSON d'estructura desconeguda amb
dynamic, davant l'enfocament tipat
- El tipus
dynamic: resolució en temps d'execució
dynamic: resolució en temps d'execucióUna variable declarada com a dynamic li diu al compilador: "no comprovis res sobre aquesta
variable ara; resol-ho quan el programa s'executi". Qualsevol accés a una propietat, un mètode o
un operador sobre una variable dynamic es posposa fins a temps d'execució, moment en què el
runtime de .NET (el Dynamic Language Runtime, DLR) intenta resoldre'l contra el tipus real del
valor que conté en aquell instant:
dynamic valor = "Rayuela";
Console.WriteLine(valor.Length); // 7: en temps d'execucio, "Rayuela" es un string, que te Length
valor = 42;
Console.WriteLine(valor.Length); // Excepcio en temps d'execucio: int no te cap propietat LengthEl primer Console.WriteLine compila i funciona perquè, en executar-se, valor conté un
string, i string sí que té Length. El segon compila igual de bé —el compilador no
comprova res sobre dynamic— però falla en temps d'execució, amb una
RuntimeBinderException, perquè int no té cap propietat Length. Aquest és el cost central
de dynamic: els errors que el compilador detectaria immediatament sobre qualsevol altre tipus
es converteixen en excepcions que només apareixen en executar el programa, potser molt després
d'haver-lo escrit.
dynamic davant object: la mateixa dada, comprovació en moments diferents
dynamic davant object: la mateixa dada, comprovació en moments diferentsobject (el tipus més general del sistema de tipus de C#, Mòdul 3) i dynamic poden semblar
similars —tots dos poden contenir qualsevol valor— però difereixen en quan es comprova el
tipus abans d'operar-hi:
object comObject = "Rayuela";
// comObject.Length; // Error de COMPILACIO: object no te Length
int longitud = ((string)comObject).Length; // cal un cast explicit primer
dynamic comDynamic = "Rayuela";
int longitudDinamica = comDynamic.Length; // compila sense cast; es resol en temps d'execucioobject |
dynamic |
|
|---|---|---|
| Comprovació de tipus | En temps de compilació (exigeix cast explícit per fer servir membres específics) | Posposada a temps d'execució |
| Accés a membres del tipus real | Requereix is/as/cast primer (Mòdul 3) |
Directe, sense cast, assumint que existirà |
| Error si el membre no existeix | En temps de compilació (no compila) | En temps d'execució (RuntimeBinderException) |
| Autocompletat de l'IDE | Complet, sobre el tipus després del cast | Cap: l'IDE no pot saber quins membres tindrà |
| Rendiment | El d'una crida normal | Més lent: cada operació es resol dinàmicament cada vegada |
A la pràctica, object obliga a ser explícit sobre quin tipus s'espera realment (amb is/as,
com al Polimorfisme del Mòdul 3) abans d'operar amb el valor; dynamic permet saltar-se aquest
pas, a canvi de perdre tota garantia que el membre accedit existirà de veritat quan el programa
s'executi.
dynamic davant var: dinàmic no és el mateix que inferit
dynamic davant var: dinàmic no és el mateix que inferitÉs un error de principiant molt comú confondre dynamic amb var (Mòdul 1), perquè tots dos
permeten ometre el nom del tipus en declarar la variable. La diferència és fonamental:
var titol = "Rayuela"; // el compilador INFEREIX que titol es string, i el fixa per sempre
// titol = 42; // Error de COMPILACIO: no es pot assignar un int a una variable string
dynamic valorDinamic = "Rayuela"; // el compilador NO fixa cap tipus concret
valorDinamic = 42; // compila perfectament: dynamic pot canviar de tipus en qualsevol momentvar és sucre sintàctic pur: el compilador dedueix el tipus exacte a partir del valor
inicial (aquí, string) i, a partir d'aquell moment, titol es comporta exactament com si
s'hagués escrit string titol = "Rayuela"; —amb totes les comprovacions de tipus habituals.
dynamic, en canvi, és un tipus real del sistema de tipus que renuncia a aquestes comprovacions:
una variable dynamic pot contenir un string en una línia i un int a la següent, sense que
el compilador es queixi en cap moment.
var |
dynamic |
|
|---|---|---|
| Què fa el compilador | Infereix el tipus real una única vegada, a la declaració | No fixa cap tipus; posposa tot a temps d'execució |
| Pot canviar de tipus la variable? | No: el tipus inferit queda fix | Sí: pot contenir valors de tipus diferents en moments diferents |
| Comprovació de tipus | Completa, en temps de compilació (com qualsevol tipus explícit) | Cap en temps de compilació |
| Cost en temps d'execució | Cap (és només una declaració més curta) | El de resoldre cada operació dinàmicament |
ExpandoObject: objectes dinàmics sense una classe definida
ExpandoObject: objectes dinàmics sense una classe definidaExpandoObject (de System.Dynamic) porta la idea de dynamic un pas més enllà: permet crear
un objecte sense cap classe que el defineixi d'antuvi, afegint-hi propietats sobre la marxa,
en temps d'execució:
using System.Dynamic;
dynamic llibreInformal = new ExpandoObject();
llibreInformal.Titol = "Rayuela";
llibreInformal.Autor = "Julio Cortazar";
llibreInformal.PaginesLlegides = 120; // cap classe va declarar mai aquesta propietat
Console.WriteLine($"{llibreInformal.Titol}, pagina {llibreInformal.PaginesLlegides}");Cap classe de BiblioTech defineix PaginesLlegides: ExpandoObject permet afegir aquesta
propietat sobre la marxa perquè, per sota, no és més que un diccionari de nom-valor disfressat
d'objecte amb sintaxi de punt. És útil en escenaris d'scripting o prototipatge molt ràpid, on
definir una classe completa per a una estructura de dades que es fa servir una sola vegada
resultaria desproporcionat; per a qualsevol dada que formi part del domini estable de
BiblioTech, una classe normal (com Llibre) continua sent molt millor opció, per les raons de
l'apartat 6.
- Quan té sentit fer servir
dynamic
dynamicdynamic no és un error de disseny de C#: existeixen escenaris reals on renunciar a la
comprovació de tipus en temps de compilació és, precisament, el que cal:
| Escenari | Per què encaixa dynamic |
|---|---|
| Interoperabilitat COM (Office, automatització d'Excel/Word des de .NET) | Les APIs COM no exposen tipus estàtics que el compilador de C# pugui verificar d'antuvi |
| JSON no tipat d'estructura variable o desconeguda | Quan no val la pena (o no és possible) definir una classe per a cada forma possible de resposta |
| Escenaris d'scripting o eines interactives | Prototipatge ràpid on la flexibilitat importa més que la seguretat de tipus |
Codi de domini tipat (BiblioTech: Llibre, Soci, Prestec...) |
No encaixa: vegeu l'apartat 6 |
- Per què evitar
dynamic en el domini de BiblioTech
dynamic en el domini de BiblioTechTot el model de BiblioTech —MaterialBibliotecari, Llibre, Revista, Soci, Prestec—
s'ha construït, mòdul a mòdul, recolzant-se precisament en el que dynamic descarta:
- Seguretat de tipus en temps de compilació:
llibre1.Isbnfalla immediatament en escriure el codi, amb un missatge clar, siIsbnno existís o estigués mal escrit; ambdynamic llibre1, el mateix error només apareixeria com a excepció en executar aquella línia concreta, potser moltes execucions després d'haver-la escrit. - Autocompletat i refactorització assistida per l'IDE: l'editor coneix, en tot moment, els
membres exactes de
Llibregràcies al tipatge estàtic; sobre una variabledynamic, l'IDE no pot oferir cap ajuda, perquè no sap quin tipus tindrà fins que el programa s'executi. - Rendiment: cada operació sobre
dynamices resol dinàmicament en temps d'execució (amb una infraestructura de resolució i una memòria cau interna del DLR), més lenta que la crida directa i ja resolta d'un tipus estàtic comLlibre.
Substituir, per exemple, Llibre llibre1 = ... per dynamic llibre1 = ... en qualsevol part de
BiblioTech no aportaria cap avantatge (el tipus real sempre es coneix d'antuvi) i sí una pèrdua
clara: errors que avui atura el compilador passarien a ser excepcions en temps d'execució, i
l'editor deixaria d'ajudar amb autocompletat. La regla general: fes servir dynamic només quan
el tipus realment no es coneix d'antuvi i no hi ha manera raonable de modelar-lo amb una
classe; en qualsevol altre cas, el tipatge estàtic de C# és l'opció correcta.
- Exemple: llegir un JSON d'estructura desconeguda amb
dynamic, davant l'enfocament tipat
dynamic, davant l'enfocament tipatLa lliçó de JSON i APIs REST (Mòdul 5) va definir MetadadesLlibreExtern i Editorial per
deserialitzar de forma tipada la resposta d'un servei extern de metadades de llibres,
l'estructura del qual es coneixia d'antuvi. Però no totes les APIs externes documenten la seva
resposta amb aquesta claredat; de vegades només es disposa d'un JSON d'exemple, d'estructura
variable, sense cap classe ja escrita per a ell. dynamic, combinat amb el tipus que retorna
JsonSerializer.Deserialize<dynamic> sobre un JsonElement, permet explorar-lo sense definir
classes per endavant:
using System.Text.Json;
string jsonDesconegut =
"""
{
"isbn": "978-84-376-0495-4",
"editorial": { "nom": "Sudamericana", "pais": "Argentina" },
"generes": ["Novel·la", "Literatura llatinoamericana"]
}
""";
// Enfocament amb dynamic: sense cap classe definida d'antuvi
JsonElement arrel = JsonSerializer.Deserialize<JsonElement>(jsonDesconegut);
dynamic document = arrel;
Console.WriteLine(document.GetProperty("isbn").GetString()); // "978-84-376-0495-4"
Console.WriteLine(document.GetProperty("editorial").GetProperty("pais").GetString()); // "Argentina"// Enfocament tipat equivalent, com a la leccio de JSON i APIs REST (Modul 5)
MetadadesLlibreExtern? metadades =
JsonSerializer.Deserialize<MetadadesLlibreExtern>(jsonDesconegut,
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
Console.WriteLine(metadades?.Isbn); // "978-84-376-0495-4"
Console.WriteLine(metadades?.Editorial.Pais); // "Argentina"Els dos fragments llegeixen la mateixa dada, però amb garanties molt diferents:
document.GetProperty("isbn") no comprova en temps de compilació que la clau "isbn" existeixi
al JSON —si no existís, o si s'escrivís malament ("ivbn"), l'error només apareixeria com a
excepció en executar aquesta línia—; metadades?.Isbn, en canvi, és una propietat real de
MetadadesLlibreExtern, verificada pel compilador, amb autocompletat a l'IDE, i amb un valor
null segur i explícit si la deserialització no troba les dades esperades. dynamic pot ser
raonable per a una exploració ràpida i puntual d'un JSON d'estructura encara no coneguda; tan
bon punt l'estructura s'estabilitza (com ja va passar amb MetadadesLlibreExtern), migrar a
classes tipades és gairebé sempre la decisió correcta per al codi que es mantindrà en el temps.
flowchart LR
A["JSON d'estructura desconeguda"] --> B{"Es coneix la forma exacta?"}
B -->|No, exploracio puntual| C["dynamic / JsonElement.GetProperty"]
B -->|Si, o es pot modelar| D["Classes tipades + JsonSerializer.Deserialize-T-"]
D --> E["Seguretat de tipus, autocompletat, millor rendiment"]
Errors Comuns i Consells
- Confondre
dynamicambvar:varés inferència de tipus en temps de compilació (el tipus queda fix);dynamicrenuncia a la comprovació de tipus completament, i una mateixa variable pot canviar de tipus en temps d'execució. - Fer servir
dynamicper evitar escriure una classe: modelar una estructura de dades estable amb una classe (comMetadadesLlibreExtern) gairebé sempre val la pena, a canvi de seguretat de tipus i autocompletat durant la resta de la vida del codi. - Propagar
dynamicpel codi de domini: un soldynamicen una signatura de mètode obliga que tot el codi que el faci servir després treballi també sense comprovació de tipus; convé aïllardynamica la vora del sistema (on de veritat es necessita, com interoperabilitat COM) i convertir a tipus concrets com més aviat millor. - No provar el camí d'error de
dynamic: com que el compilador no avisa de res, un error amb una propietat mal escrita (document.GetProperty("ivbn")) només es descobreix executant aquesta línia exacta; sense proves que cobreixin aquest camí, l'error pot passar desapercebut fins a producció. - Consell: si et sorprens escrivint
dynamical codi de domini d'una aplicació com BiblioTech, és un senyal que probablement falta una classe per definir, no una raó per continuar endavant ambdynamic.
Exercicis
-
Declara una variable
dynamic valoramb el text"BiblioTech", imprimeixvalor.Length, i després reassigna-li el número2026. Explica (en un comentari) per què aquesta reassignació compila sense error, a diferència d'intentar el mateix amb una variablevar. -
Fent servir
JsonSerializer.Deserialize<JsonElement>i una variabledynamic, extreu el primer element de l'array"generes"del JSON d'exemple de l'apartat 7 (pista:document.GetProperty("generes")[0].GetString()). -
Explica, en un paràgraf breu, per què convertir
Llibreendynamic Llibre(és a dir, declarardynamic llibre1 = new Llibre(...)en comptes deLlibre llibre1 = new Llibre(...)) a BiblioTech seria una mala decisió de disseny, citant almenys dues de les raons vistes a l'apartat 6.
Solucions
dynamic valor = "BiblioTech";
Console.WriteLine(valor.Length); // 10
valor = 2026; // Compila: dynamic no fixa cap tipus concret, pot contenir qualsevol valor
// Amb "var valor = "BiblioTech";", "valor = 2026;" seria un error de COMPILACIO,
// perque var infereix string una unica vegada i el tipus queda fix per sempre.
using System.Text.Json;
string jsonDesconegut =
"""
{
"isbn": "978-84-376-0495-4",
"editorial": { "nom": "Sudamericana", "pais": "Argentina" },
"generes": ["Novel·la", "Literatura llatinoamericana"]
}
""";
JsonElement arrel = JsonSerializer.Deserialize<JsonElement>(jsonDesconegut);
dynamic document = arrel;
Console.WriteLine(document.GetProperty("generes")[0].GetString()); // "Novel·la"
Convertir Llibre en dynamic eliminaria la comprovació en temps de compilació de tot el
codi que faci servir llibre1 (un error com llibre1.Isbnn —amb errata— passaria d'un error
de compilació immediat a una excepció en temps d'execució, possiblement en producció); i
l'IDE deixaria de poder oferir autocompletat sobre les seves propietats i mètodes, ja que no
podria saber d'antuvi quins membres tindrà llibre1. A més, cada accés als seus membres seria
més lent, en resoldre's dinàmicament a cada crida en lloc d'estar ja resolt pel compilador.
Conclusió
En aquesta lliçó has vist el tipus dynamic: com posposa tota comprovació de tipus a temps
d'execució, en què es diferencia d'object (que exigeix cast explícit) i de var (que
infereix un tipus fix en temps de compilació), què és ExpandoObject, i en quins escenaris
reals (interoperabilitat COM, JSON no tipat, scripting) té sentit fer-lo servir. També has
confirmat per què el domini tipat de BiblioTech ha de continuar recolzant-se en classes concretes
com Llibre en comptes de dynamic, i vist un exemple d'exploració de JSON no tipat davant
l'enfocament tipat ja utilitzat al Mòdul 5.
La lliçó següent, Gestió de Memòria i Recollida d'Escombraries, deixa enrere el sistema de tipus
per entrar en com .NET gestiona la memòria de qualsevol objecte —tipat o dinàmic per igual—:
quina diferència hi ha entre la pila (stack) i el munt (heap), com decideix el recol·lector
d'escombraries quan alliberar un objecte, i com alliberar explícitament recursos externs
(fitxers, connexions) amb IDisposable, retomant el patró using ja vist al Mòdul 5.
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
