Todo el código de BiblioTech escrito hasta ahora es fuertemente tipado: el compilador conoce, en cada línea, el tipo exacto de cada variable (Libro, string, List<Socio>...) y rechaza cualquier operación que no encaje —acceder a una propiedad inexistente, pasar un argumento del tipo equivocado— antes de que el programa llegue a ejecutarse. C# ofrece, además, un tipo especial que renuncia deliberadamente a esa comprobación: dynamic. Esta lección explica qué es, en qué se diferencia de object y de var (con los que se confunde con frecuencia), cuándo tiene sentido usarlo, y por qué el dominio de BiblioTech, tipado de forma consciente desde el Módulo 1, debe seguir evitándolo en su código de negocio.

Contenido

  1. El tipo dynamic: resolución en tiempo de ejecución
  2. dynamic frente a object: mismo dato, comprobación en momentos distintos
  3. dynamic frente a var: dinámico no es lo mismo que inferido
  4. ExpandoObject: objetos dinámicos sin una clase definida
  5. Cuándo tiene sentido usar dynamic
  6. Por qué evitar dynamic en el dominio de BiblioTech
  7. Ejemplo: leer un JSON de estructura desconocida con dynamic, frente al enfoque tipado

  1. El tipo dynamic: resolución en tiempo de ejecución

Una variable declarada como dynamic le dice al compilador: "no compruebes nada sobre esta variable ahora; resuélvelo cuando el programa se ejecute". Cualquier acceso a una propiedad, un método o un operador sobre una variable dynamic se pospone hasta tiempo de ejecución, momento en el que el runtime de .NET (el Dynamic Language Runtime, DLR) intenta resolverlo contra el tipo real del valor que contiene en ese instante:

dynamic valor = "Rayuela";
Console.WriteLine(valor.Length); // 7: en tiempo de ejecucion, "Rayuela" es un string, que tiene Length

valor = 42;
Console.WriteLine(valor.Length); // Excepcion en tiempo de ejecucion: int no tiene una propiedad Length

El primer Console.WriteLine compila y funciona porque, al ejecutarse, valor contiene un string, y string sí tiene Length. El segundo compila igual de bien —el compilador no comprueba nada sobre dynamic— pero falla en tiempo de ejecución, con una RuntimeBinderException, porque int no tiene ninguna propiedad Length. Este es el coste central de dynamic: los errores que el compilador detectaría de inmediato sobre cualquier otro tipo se convierten en excepciones que solo aparecen al ejecutar el programa, quizás mucho después de haberlo escrito.

  1. dynamic frente a object: mismo dato, comprobación en momentos distintos

object (el tipo más general del sistema de tipos de C#, Módulo 3) y dynamic pueden parecer similares —ambos pueden contener cualquier valor— pero difieren en cuándo se comprueba el tipo antes de operar con él:

object comoObject = "Rayuela";
// comoObject.Length;         // Error de COMPILACION: object no tiene Length
int longitud = ((string)comoObject).Length; // hace falta un cast explicito primero

dynamic comoDynamic = "Rayuela";
int longitudDinamica = comoDynamic.Length;  // compila sin cast; se resuelve en tiempo de ejecucion
object dynamic
Comprobación de tipos En tiempo de compilación (exige cast explícito para usar miembros específicos) Pospuesta a tiempo de ejecución
Acceso a miembros del tipo real Requiere is/as/cast primero (Módulo 3) Directo, sin cast, asumiendo que existirá
Error si el miembro no existe En tiempo de compilación (no compila) En tiempo de ejecución (RuntimeBinderException)
Autocompletado del IDE Completo, sobre el tipo tras el cast Ninguno: el IDE no puede saber qué miembros tendrá
Rendimiento El de una llamada normal Más lento: cada operación se resuelve dinámicamente cada vez

En la práctica, object obliga a ser explícito sobre qué tipo se espera realmente (con is/as, como en el Polimorfismo del Módulo 3) antes de operar con el valor; dynamic permite saltarse ese paso, a cambio de perder toda garantía de que el miembro accedido existirá de verdad cuando el programa se ejecute.

  1. dynamic frente a var: dinámico no es lo mismo que inferido

Es un error de principiante muy común confundir dynamic con var (Módulo 1), porque ambos permiten omitir el nombre del tipo al declarar la variable. La diferencia es fundamental:

var titulo = "Rayuela";       // el compilador INFIERE que titulo es string, y lo fija para siempre
// titulo = 42;                // Error de COMPILACION: no se puede asignar un int a una variable string

dynamic valorDinamico = "Rayuela"; // el compilador NO fija ningun tipo concreto
valorDinamico = 42;                 // compila perfectamente: dynamic puede cambiar de tipo en cualquier momento

var es azúcar sintáctico puro: el compilador deduce el tipo exacto a partir del valor inicial (aquí, string) y, a partir de ese momento, titulo se comporta exactamente como si se hubiera escrito string titulo = "Rayuela"; —con todas las comprobaciones de tipos habituales. dynamic, en cambio, es un tipo real del sistema de tipos que renuncia a esas comprobaciones: una variable dynamic puede contener un string en una línea y un int en la siguiente, sin que el compilador se queje en ningún momento.

var dynamic
Qué hace el compilador Infiere el tipo real una única vez, en la declaración No fija ningún tipo; pospone todo a tiempo de ejecución
¿Puede cambiar de tipo la variable? No: el tipo inferido queda fijo Sí: puede contener valores de tipos distintos en momentos distintos
Comprobación de tipos Completa, en tiempo de compilación (como cualquier tipo explícito) Ninguna en tiempo de compilación
Coste en tiempo de ejecución Ninguno (es solo una declaración más corta) El de resolver cada operación dinámicamente

  1. ExpandoObject: objetos dinámicos sin una clase definida

ExpandoObject (de System.Dynamic) lleva la idea de dynamic un paso más allá: permite crear un objeto sin ninguna clase que lo defina de antemano, añadiéndole propiedades sobre la marcha, en tiempo de ejecución:

using System.Dynamic;

dynamic libroInformal = new ExpandoObject();
libroInformal.Titulo = "Rayuela";
libroInformal.Autor = "Julio Cortazar";
libroInformal.PaginasLeidas = 120; // ninguna clase declaro jamas esta propiedad

Console.WriteLine($"{libroInformal.Titulo}, pagina {libroInformal.PaginasLeidas}");

Ninguna clase de BiblioTech define PaginasLeidas: ExpandoObject permite añadir esa propiedad sobre la marcha porque, por debajo, no es más que un diccionario de nombre-valor disfrazado de objeto con sintaxis de punto. Es útil en escenarios de scripting o prototipado muy rápido, donde definir una clase completa para una estructura de datos que se usa una sola vez resultaría desproporcionado; para cualquier dato que forme parte del dominio estable de BiblioTech, una clase normal (como Libro) sigue siendo muchísimo mejor opción, por las razones del apartado 6.

  1. Cuándo tiene sentido usar dynamic

dynamic no es un error de diseño de C#: existen escenarios reales donde renunciar a la comprobación de tipos en tiempo de compilación es, precisamente, lo que se necesita:

Escenario Por qué encaja dynamic
Interoperabilidad COM (Office, automatización de Excel/Word desde .NET) Las APIs COM no exponen tipos estáticos que el compilador de C# pueda verificar de antemano
JSON no tipado de estructura variable o desconocida Cuando no merece la pena (o no es posible) definir una clase para cada forma posible de respuesta
Escenarios de scripting o herramientas interactivas Prototipado rápido donde la flexibilidad importa más que la seguridad de tipos
Código de dominio tipado (BiblioTech: Libro, Socio, Prestamo...) No encaja: ver apartado 6

  1. Por qué evitar dynamic en el dominio de BiblioTech

Todo el modelo de BiblioTech —MaterialBibliotecario, Libro, Revista, Socio, Prestamo— se ha construido, módulo a módulo, apoyándose precisamente en lo que dynamic descarta:

  • Seguridad de tipos en tiempo de compilación: libro1.Isbn falla de inmediato al escribir el código, con un mensaje claro, si Isbn no existiera o estuviera mal escrito; con dynamic libro1, el mismo error solo aparecería como excepción al ejecutar esa línea concreta, quizás muchas ejecuciones después de escribirla.
  • Autocompletado y refactorización asistida por el IDE: el editor conoce, en todo momento, los miembros exactos de Libro gracias al tipado estático; sobre una variable dynamic, el IDE no puede ofrecer ninguna ayuda, porque no sabe qué tipo tendrá hasta que el programa se ejecute.
  • Rendimiento: cada operación sobre dynamic se resuelve dinámicamente en tiempo de ejecución (con una infraestructura de resolución y una caché interna del DLR), más lenta que la llamada directa y ya resuelta de un tipo estático como Libro.

Sustituir, por ejemplo, Libro libro1 = ... por dynamic libro1 = ... en cualquier parte de BiblioTech no aportaría ninguna ventaja (el tipo real siempre se conoce de antemano) y sí una pérdida clara: errores que hoy detiene el compilador pasarían a ser excepciones en tiempo de ejecución, y el editor dejaría de ayudar con autocompletado. La regla general: usa dynamic solo cuando el tipo realmente no se conoce de antemano y no hay forma razonable de modelarlo con una clase; en cualquier otro caso, el tipado estático de C# es la opción correcta.

  1. Ejemplo: leer un JSON de estructura desconocida con dynamic, frente al enfoque tipado

La lección de JSON y APIs REST (Módulo 5) definió MetadatosLibroExterno y Editorial para deserializar de forma tipada la respuesta de un servicio externo de metadatos de libros, cuya estructura se conocía de antemano. Pero no todas las APIs externas documentan su respuesta con esa claridad; a veces solo se dispone de un JSON de ejemplo, de estructura variable, sin ninguna clase ya escrita para él. dynamic, combinado con el tipo dynamic que devuelve JsonSerializer.Deserialize<dynamic> sobre un JsonElement, permite explorarlo sin definir clases por adelantado:

using System.Text.Json;

string jsonDesconocido =
    """
    {
      "isbn": "978-84-376-0495-4",
      "editorial": { "nombre": "Sudamericana", "pais": "Argentina" },
      "generos": ["Novela", "Literatura latinoamericana"]
    }
    """;

// Enfoque con dynamic: sin ninguna clase definida de antemano
JsonElement raiz = JsonSerializer.Deserialize<JsonElement>(jsonDesconocido);
dynamic documento = raiz;

Console.WriteLine(documento.GetProperty("isbn").GetString());                  // "978-84-376-0495-4"
Console.WriteLine(documento.GetProperty("editorial").GetProperty("pais").GetString()); // "Argentina"
// Enfoque tipado equivalente, como en la leccion de JSON y APIs REST (Modulo 5)
MetadatosLibroExterno? metadatos =
    JsonSerializer.Deserialize<MetadatosLibroExterno>(jsonDesconocido,
        new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

Console.WriteLine(metadatos?.Isbn);              // "978-84-376-0495-4"
Console.WriteLine(metadatos?.Editorial.Pais);     // "Argentina"

Ambos fragmentos leen el mismo dato, pero con garantías muy distintas: documento.GetProperty("isbn") no comprueba en tiempo de compilación que la clave "isbn" exista en el JSON —si no existiera, o si se escribiera mal ("ivbn"), el error solo aparecería como excepción al ejecutar esa línea—; metadatos?.Isbn, en cambio, es una propiedad real de MetadatosLibroExterno, verificada por el compilador, con autocompletado en el IDE, y con un valor null seguro y explícito si la deserialización no encuentra los datos esperados. dynamic puede ser razonable para una exploración rápida y puntual de un JSON de estructura todavía no conocida; en cuanto la estructura se estabiliza (como ya ocurrió con MetadatosLibroExterno), migrar a clases tipadas es casi siempre la decisión correcta para el código que va a mantenerse en el tiempo.

flowchart LR
    A["JSON de estructura desconocida"] --> B{"Se conoce la forma exacta?"}
    B -->|No, exploracion puntual| C["dynamic / JsonElement.GetProperty"]
    B -->|Si, o se puede modelar| D["Clases tipadas + JsonSerializer.Deserialize-T-"]
    D --> E["Seguridad de tipos, autocompletado, mejor rendimiento"]

Errores Comunes y Consejos

  • Confundir dynamic con var: var es inferencia de tipos en tiempo de compilación (el tipo queda fijo); dynamic renuncia a la comprobación de tipos por completo, y una misma variable puede cambiar de tipo en tiempo de ejecución.
  • Usar dynamic para evitar escribir una clase: modelar una estructura de datos estable con una clase (como MetadatosLibroExterno) casi siempre compensa el esfuerzo inicial, a cambio de seguridad de tipos y autocompletado durante el resto de la vida del código.
  • Propagar dynamic por el código de dominio: un solo dynamic en una firma de método obliga a que todo el código que lo usa después trabaje también sin comprobación de tipos; conviene aislar dynamic en el borde del sistema (donde de verdad se necesita, como interoperabilidad COM) y convertir a tipos concretos cuanto antes.
  • No probar el camino de error de dynamic: como el compilador no avisa de nada, un típo con una propiedad mal escrita (documento.GetProperty("ivbn")) solo se descubre ejecutando esa línea exacta; sin pruebas que cubran ese camino, el error puede pasar desapercibido hasta producción.
  • Consejo: si te sorprendes escribiendo dynamic en el código de dominio de una aplicación como BiblioTech, es una señal de que probablemente falta una clase por definir, no una razón para seguir adelante con dynamic.

Ejercicios

  1. Declara una variable dynamic valor con el texto "BiblioTech", imprime valor.Length, y después reasígnale el número 2026. Explica (en un comentario) por qué esa reasignación compila sin error, a diferencia de intentar lo mismo con una variable var.

  2. Usando JsonSerializer.Deserialize<JsonElement> y una variable dynamic, extrae el primer elemento del array "generos" del JSON de ejemplo del apartado 7 (pista: documento.GetProperty("generos")[0].GetString()).

  3. Explica, en un párrafo breve, por qué convertir Libro en dynamic Libro (es decir, declarar dynamic libro1 = new Libro(...) en vez de Libro libro1 = new Libro(...)) en BiblioTech sería una mala decisión de diseño, citando al menos dos de las razones vistas en el apartado 6.

Soluciones

dynamic valor = "BiblioTech";
Console.WriteLine(valor.Length); // 10

valor = 2026; // Compila: dynamic no fija ningun tipo concreto, puede contener cualquier valor
// Con "var valor = "BiblioTech";", "valor = 2026;" seria un error de COMPILACION,
// porque var infiere string una unica vez y el tipo queda fijo para siempre.
using System.Text.Json;

string jsonDesconocido =
    """
    {
      "isbn": "978-84-376-0495-4",
      "editorial": { "nombre": "Sudamericana", "pais": "Argentina" },
      "generos": ["Novela", "Literatura latinoamericana"]
    }
    """;

JsonElement raiz = JsonSerializer.Deserialize<JsonElement>(jsonDesconocido);
dynamic documento = raiz;

Console.WriteLine(documento.GetProperty("generos")[0].GetString()); // "Novela"

Convertir Libro en dynamic eliminaría la comprobación en tiempo de compilación de todo el código que use libro1 (un error como libro1.Isbnn —con errata— pasaría de un error de compilación inmediato a una excepción en tiempo de ejecución, posiblemente en producción); y el IDE dejaría de poder ofrecer autocompletado sobre sus propiedades y métodos, ya que no podría saber de antemano qué miembros tendrá libro1. Además, cada acceso a sus miembros sería más lento, al resolverse dinámicamente en cada llamada en vez de estar ya resuelto por el compilador.

Conclusión

En esta lección has visto el tipo dynamic: cómo pospone toda comprobación de tipos a tiempo de ejecución, en qué se diferencia de object (que exige cast explícito) y de var (que infiere un tipo fijo en tiempo de compilación), qué es ExpandoObject, y en qué escenarios reales (interoperabilidad COM, JSON no tipado, scripting) tiene sentido usarlo. También has confirmado por qué el dominio tipado de BiblioTech debe seguir apoyándose en clases concretas como Libro en vez de dynamic, y visto un ejemplo de exploración de JSON no tipado frente al enfoque tipado ya usado en el Módulo 5.

La siguiente lección, Gestión de Memoria y Recolección de Basura, deja atrás el sistema de tipos para entrar en cómo .NET gestiona la memoria de cualquier objeto —tipado o dinámico por igual—: qué diferencia hay entre la pila (stack) y el montón (heap), cómo decide el recolector de basura cuándo liberar un objeto, y cómo liberar explícitamente recursos externos (ficheros, conexiones) con IDisposable, retomando el patrón using ya visto en el Módulo 5.

Curso de Programación en C#

Módulo 1: Introducción a C#

Módulo 2: Estructuras de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Conceptos Avanzados de C#

Módulo 5: Trabajando con Datos

Módulo 6: Temas Avanzados

Módulo 7: Construcción de Aplicaciones

Módulo 8: Mejores Prácticas y Patrones de Diseño

Módulo 9: Proyecto Final

© Copyright 2026. Todos los derechos reservados