1. Introducción
C# es un representante de los lenguajes fuertemente tipados. Eso significa que en la etapa de compilación una variable debe tener un tipo definido, y acceder a los miembros de una clase solo se puede de acuerdo con ese tipo. Esto es seguro, cómodo y claro para la IDE y el compilador.
Pero a veces hace falta tratar con objetos cuya estructura puede ser desconocida en tiempo de compilación — por ejemplo, cuando:
- Se ejecuta código escrito en otro lenguaje .NET (por ejemplo IronPython o IronRuby), donde la tipificación es dinámica.
- Al trabajar con objetos COM (por ejemplo, con Microsoft Office vía Interop).
- Al interactuar con estructuras de datos dinámicas: por ejemplo, objetos JSON poco formalizados.
- No quieres escribir un montón de código repetitivo, y es más fácil "confiar" en el objeto y probar llamar al método que necesitas por nombre.
Con la aparición en C# 4.0 de la palabra clave dynamic y la plataforma DLR surgió la posibilidad de escribir código que se comporte "como en Python, pero en C#": la comprobación se hará en tiempo de ejecución, no en la compilación. Con esta palabra mágica puedes hacer que el compilador sea menos estricto y verificar manualmente si existe el método/propiedad solo cuando el programa realmente llegue a ese punto.
¿Qué es DLR?
Dynamic Language Runtime (DLR) es una extensión infraestructural de la CLR (Common Language Runtime) que permite implementar soporte para lenguajes dinámicos (y comportamiento dinámico en lenguajes estáticos). El DLR se encarga de ejecutar operaciones que el compilador no puede comprobar: por ejemplo, llamar a un método por nombre cuando el tipo solo será conocido en tiempo de ejecución. Gracias al DLR, C# obtiene un "modo dinámico".
2. La palabra clave dynamic: cómo funciona
Diferencia respecto a object
Algunos novatos piensan que dynamic es simplemente una forma más moderna de escribir object y hacer casting cada vez. No es así.
- Una variable de tipo object siempre requiere un casteo explícito al tipo concreto para llamar métodos (y el compilador lo comprueba).
- Una variable de tipo dynamic da la orden al compilador: "confía en mí, yo sé qué tipo habrá — lo resolveremos en el lugar". Si el método no existe — habrá una excepción en tiempo de ejecución.
Ejemplo:
// Ejemplo con object
object obj = "Hello, C#!";
// obj.ToUpper(); // ¡Error de compilación! El compilador no sabe que obj es una cadena
string s = ((string)obj).ToUpper(); // Solo así
// Ejemplo con dynamic
dynamic dyn = "Hello, C#!";
string upper = dyn.ToUpper(); // El compilador no protesta, la comprobación será en runtime
Con dynamic puedes acceder a propiedades y métodos inexistentes. Si no existen — se lanzará una excepción del tipo RuntimeBinderException.
Uso en el ejemplo de la aplicación
Supongamos que en nuestra mini-aplicación hay un bloque que trabaja con datos JSON cuya estructura puede cambiar sobre la marcha. Con dynamic puedes acceder a propiedades como si siempre existieran:
dynamic user = new System.Dynamic.ExpandoObject();
user.Name = "Vasya";
user.Age = 35;
Console.WriteLine($"Name: {user.Name}, Age: {user.Age}");
3. DLR en acción: qué pasa bajo el capó
Cuando una variable se declara como dynamic, el compilador de C# recuerda que no puede comprobar de antemano accesos a métodos, propiedades, indexadores, operadores, etc. sobre ese objeto.
En lugar de eso, en el lugar de cualquier llamada dinámica se inserta código que realiza una llamada al "dispatcher" dinámico del sistema. El DLR observa: ¿existe realmente ese método o propiedad al que nos estamos refiriendo de forma amplia, descuidada y optimista? Si sí — ejecuta la llamada, si no — lanza una excepción.
Esquema visual
+------------------------+
| Código con dynamic |
+----------+-------------+
|
v
+----------+-------------+
| El compilador C# inserta|
| "consulta al DLR" |
+----------+-------------+
|
v
+----------+-------------+
| En runtime el DLR busca |
| método/propiedad |
+----------+-------------+
|
/---------\
| |
encontrado no encontrado
| |
v v
llamada excepción
al método (RuntimeBinderException)
El DLR se encarga de buscar métodos y propiedades en el objeto — ¡y hasta hace caching de llamadas dinámicas usadas frecuentemente!
Un poco de historia
El DLR se creó para dar soporte a lenguajes originalmente dinámicos: IronPython, IronRuby, y también para facilitar la integración con objetos dinámicos en C#. Hoy en día se usa activamente al trabajar con objetos tipo ExpandoObject, DynamicObject, así como con COM y datos serializados dinámicamente.
4. Comparación: static vs dynamic
Comparemos cómo se ven las llamadas a propiedades y métodos en la paradigma estática y dinámica:
| Método | Declaración | Comprobación en compilación | Comportamiento ante error |
|---|---|---|---|
|
|
Requiere casteo al tipo necesario | Error de compilación o InvalidCastException |
|
|
No, todo en runtime | RuntimeBinderException, si no existe el método |
| tipo estático | |
Comprobado por el compilador | Error de compilación |
Ejemplo para ilustrar:
// Tipado estático
MyUser u = new MyUser();
u.PrintInfo(); // Comprobado por el compilador
// dynamic
dynamic du = new MyUser();
du.PrintInfo(); // El compilador no comprueba, encontrará el método solo en runtime
// object
object ou = new MyUser();
// ou.PrintInfo(); // Error de compilación
((MyUser)ou).PrintInfo(); // Hay que castear explícitamente
Métodos estáticos y dynamic
dynamic d = "Hello";
Console.WriteLine(d.Length); // 5 — todo bien
Console.WriteLine(d.FakeMethod()); // RuntimeBinderException en tiempo de ejecución
¡Pero hay que tener cuidado! Si te equivocas al escribir el nombre o el método no existe — esto se manifestará solo en tiempo de ejecución. Ejemplo clásico de "error en producción".
5. Ejemplos prácticos: dinámica en tareas reales
Trabajo con ExpandoObject
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "Sergey";
person.Greet = (Action)(() => Console.WriteLine($"Hello, {person.Name}!"));
person.Greet(); // Mostrará: Hello, Sergey!
Aquí añadimos sobre la marcha la propiedad Name e incluso el delegado Greet. Muy parecido a trabajar con objetos en JavaScript.
Enlace a estructuras JSON
using System.Dynamic;
using Newtonsoft.Json;
string json = @"{""name"": ""Andrey"", ""age"": 30}";
dynamic obj = JsonConvert.DeserializeObject
(json); Console.WriteLine(obj.name); // Andrey Console.WriteLine(obj.age); // 30
Ten en cuenta: si escribes obj.surname — será un error en tiempo de ejecución.
COM-Interop (Automatización de Office)
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true; // "Vemos" la propiedad aunque el compilador no conozca su tipo
6. Matices útiles
DLR, dynamic y uso con otros lenguajes .NET
- IronPython/IronRuby: puedes instanciar objetos de esos lenguajes y llamar a sus métodos desde C# vía dynamic.
- Objetos COM: wrapper automático de métodos y propiedades mediante dynamic.
- Objetos dinámicos personalizados: implementando la interfaz IDynamicMetaObjectProvider, puedes crear tu clase con llamadas dinámicas.
Ejemplo de integración con IronPython
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
ScriptEngine engine = Python.CreateEngine();
dynamic py = engine.Execute("class Test: pass\nTest()");
py.x = 42;
Console.WriteLine(py.x); // 42
Cómo implementar tus propios tipos dinámicos
Normalmente es más sencillo heredar de DynamicObject.
using System.Dynamic;
class MyDynamic : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "Ping")
{
result = "Pong!";
return true;
}
result = null;
return false;
}
}
dynamic d = new MyDynamic();
Console.WriteLine(d.Ping); // Pong!
Console.WriteLine(d.Miss); // null (no lanza excepción)
Cuándo dynamic es perjudicial
- Reduce el rendimiento (las llamadas dinámicas son más lentas que las estáticas).
- Complica la depuración y el mantenimiento.
- Te priva de muchas ventajas del IDE: autocompletado, refactorización, búsqueda por usos.
- Pueda provocar errores que se detectan solo en el usuario o en el servidor.
Cuándo realmente usar dynamic
- Integración con bibliotecas externas donde es imposible o poco práctico escribir wrappers (COM, Python, JavaScript).
- Manipular estructuras poco tipadas (JSON/XML/ExpandoObject).
- Para escribir frameworks, metaprogramación y APIs flexibles que por diseño deben ser dinámicas.
En todos los demás casos es mejor usar tipos estáticos y disfrutar de la ayuda del compilador :-)
7. Errores típicos al trabajar con dynamic y DLR
Error nº1: Acceder a miembros inexistentes.
Llamar a un método o propiedad inexistente en dynamic conduce a RuntimeBinderException en runtime, lo que es difícil de atrapar en la etapa de desarrollo.
Error nº2: Ignorar la comprobación de tipos.
Sin comprobar tipos antes de la llamada (por ejemplo, if (obj is IDictionary<string, object>)) puedes obtener errores inesperados en runtime.
Error nº3: Usar dynamic en APIs públicas.
Los tipos dinámicos complican el mantenimiento del código, ya que la IDE y los analizadores no pueden sugerir miembros correctos, lo que reduce la legibilidad.
Error nº4: Abusar de dynamic en código crítico de rendimiento.
Las llamadas dinámicas son más lentas que las estáticas y aumentan la sobrecarga. Usa dynamic solo cuando sea necesario.
GO TO FULL VERSION