Tipos anónimos y dinámicos
C# ofrece dos características poderosas que proporcionan flexibilidad adicional en el manejo de tipos: los tipos anónimos y los tipos dinámicos. Estas funcionalidades permiten crear objetos sin definir clases explícitas y trabajar con tipos que se resuelven en tiempo de ejecución, respectivamente.
Los tipos anónimos son especialmente útiles cuando necesitas crear objetos temporales con un conjunto específico de propiedades, mientras que los tipos dinámicos te permiten interactuar con APIs externas, objetos COM, o trabajar con JSON de manera más flexible. Aunque estas características añaden flexibilidad, también requieren un uso cuidadoso para mantener la seguridad de tipos y el rendimiento.
En este artículo exploraremos cómo crear y utilizar tipos anónimos, cómo trabajar con el tipo dynamic
, sus ventajas, limitaciones y las mejores prácticas para su implementación en aplicaciones reales.
Tipos anónimos
Los tipos anónimos permiten crear objetos con propiedades específicas sin necesidad de definir una clase explícita. El compilador genera automáticamente una clase temporal con las propiedades especificadas.
Características de los tipos anónimos
Característica | Descripción |
---|---|
Definición | Se crean usando la sintaxis new { } |
Propiedades | Solo propiedades de solo lectura |
Tipo | Generado automáticamente por el compilador |
Ámbito | Local al método donde se definen |
Herencia | Heredan de object |
Sintaxis básica
using System;
using System.Linq;
class Program
{
static void Main()
{
// Creación básica de tipo anónimo
var persona = new { Nombre = "Ana", Edad = 25, Ciudad = "Madrid" };
Console.WriteLine($"Nombre: {persona.Nombre}");
Console.WriteLine($"Edad: {persona.Edad}");
Console.WriteLine($"Ciudad: {persona.Ciudad}");
Console.WriteLine($"Tipo: {persona.GetType().Name}");
// Tipo anónimo con diferentes tipos de datos
var producto = new
{
Id = 1001,
Nombre = "Laptop",
Precio = 899.99m,
Disponible = true,
FechaLanzamiento = new DateTime(2024, 6, 15),
Categoria = new { Id = 5, Nombre = "Tecnología" }
};
Console.WriteLine($"\nProducto: {producto.Nombre}");
Console.WriteLine($"Precio: {producto.Precio:C}");
Console.WriteLine($"Categoría: {producto.Categoria.Nombre}");
// Array de tipos anónimos
var empleados = new[]
{
new { Nombre = "Carlos", Departamento = "IT", Salario = 45000 },
new { Nombre = "María", Departamento = "Ventas", Salario = 38000 },
new { Nombre = "José", Departamento = "IT", Salario = 52000 }
};
Console.WriteLine("\nEmpleados:");
foreach (var emp in empleados)
{
Console.WriteLine($"{emp.Nombre} - {emp.Departamento}: {emp.Salario:C}");
}
}
}
Proyecciones con LINQ
Los tipos anónimos son especialmente útiles con LINQ para crear proyecciones de datos:
using System;
using System.Collections.Generic;
using System.Linq;
public class Estudiante
{
public string Nombre { get; set; }
public string Apellido { get; set; }
public int Edad { get; set; }
public string Carrera { get; set; }
public double NotaMedia { get; set; }
public DateTime FechaInscripcion { get; set; }
}
class Program
{
static void Main()
{
var estudiantes = new List<Estudiante>
{
new Estudiante { Nombre = "Ana", Apellido = "García", Edad = 20, Carrera = "Informática", NotaMedia = 8.5, FechaInscripcion = new DateTime(2022, 9, 1) },
new Estudiante { Nombre = "Luis", Apellido = "Martín", Edad = 22, Carrera = "Matemáticas", NotaMedia = 9.2, FechaInscripcion = new DateTime(2021, 9, 1) },
new Estudiante { Nombre = "Carmen", Apellido = "López", Edad = 19, Carrera = "Informática", NotaMedia = 7.8, FechaInscripcion = new DateTime(2023, 9, 1) },
new Estudiante { Nombre = "David", Apellido = "Ruiz", Edad = 21, Carrera = "Física", NotaMedia = 8.9, FechaInscripcion = new DateTime(2022, 9, 1) }
};
// Proyección simple con tipo anónimo
var resumenEstudiantes = estudiantes.Select(e => new
{
NombreCompleto = $"{e.Nombre} {e.Apellido}",
e.Carrera,
e.NotaMedia,
Categoria = e.NotaMedia >= 9.0 ? "Excelente" :
e.NotaMedia >= 8.0 ? "Muy Bueno" :
e.NotaMedia >= 7.0 ? "Bueno" : "Regular"
}).ToList();
Console.WriteLine("Resumen de estudiantes:");
foreach (var estudiante in resumenEstudiantes)
{
Console.WriteLine($"{estudiante.NombreCompleto} - {estudiante.Carrera}");
Console.WriteLine($" Nota: {estudiante.NotaMedia} ({estudiante.Categoria})");
}
// Agrupación con tipos anónimos
var estudiantesPorCarrera = estudiantes
.GroupBy(e => e.Carrera)
.Select(g => new
{
Carrera = g.Key,
NumeroEstudiantes = g.Count(),
NotaMediaCarrera = g.Average(e => e.NotaMedia),
MejorEstudiante = g.OrderByDescending(e => e.NotaMedia).First().Nombre
});
Console.WriteLine("\nEstadísticas por carrera:");
foreach (var carrera in estudiantesPorCarrera)
{
Console.WriteLine($"{carrera.Carrera}:");
Console.WriteLine($" Estudiantes: {carrera.NumeroEstudiantes}");
Console.WriteLine($" Nota media: {carrera.NotaMediaCarrera:F2}");
Console.WriteLine($" Mejor estudiante: {carrera.MejorEstudiante}");
}
// Proyección compleja con cálculos
var informeCompleto = estudiantes
.Where(e => e.NotaMedia >= 8.0)
.Select(e => new
{
e.Nombre,
e.Carrera,
e.NotaMedia,
AñosEstudio = DateTime.Now.Year - e.FechaInscripcion.Year,
RendimientoAnual = e.NotaMedia / (DateTime.Now.Year - e.FechaInscripcion.Year + 1),
Estado = new
{
EsVeterano = DateTime.Now.Year - e.FechaInscripcion.Year >= 3,
EsDestacado = e.NotaMedia >= 9.0,
Recomendacion = e.NotaMedia >= 9.0 ? "Beca de excelencia" : "Continuar seguimiento"
}
})
.OrderByDescending(e => e.NotaMedia);
Console.WriteLine("\nInforme de estudiantes destacados:");
foreach (var info in informeCompleto)
{
Console.WriteLine($"{info.Nombre} ({info.Carrera}):");
Console.WriteLine($" Nota: {info.NotaMedia} - Años estudio: {info.AñosEstudio}");
Console.WriteLine($" Rendimiento anual: {info.RendimientoAnual:F2}");
Console.WriteLine($" ¿Veterano?: {info.Estado.EsVeterano}");
Console.WriteLine($" Recomendación: {info.Estado.Recomendacion}");
}
}
}
Igualdad en tipos anónimos
Los tipos anónimos implementan automáticamente métodos de igualdad basados en las propiedades:
using System;
class Program
{
static void Main()
{
// Dos instancias con los mismos valores
var punto1 = new { X = 10, Y = 20 };
var punto2 = new { X = 10, Y = 20 };
var punto3 = new { X = 15, Y = 25 };
Console.WriteLine($"punto1 == punto2: {punto1.Equals(punto2)}"); // True
Console.WriteLine($"punto1 == punto3: {punto1.Equals(punto3)}"); // False
// GetHashCode también está implementado
Console.WriteLine($"HashCode punto1: {punto1.GetHashCode()}");
Console.WriteLine($"HashCode punto2: {punto2.GetHashCode()}");
Console.WriteLine($"HashCode punto3: {punto3.GetHashCode()}");
// ToString está sobrescrito
Console.WriteLine($"punto1.ToString(): {punto1.ToString()}");
// Comparación con diferentes tipos anónimos (mismo contenido, diferente orden)
var persona1 = new { Nombre = "Ana", Edad = 25 };
var persona2 = new { Edad = 25, Nombre = "Ana" }; // Diferente orden
// Estos son tipos diferentes aunque tengan las mismas propiedades
Console.WriteLine($"Mismo tipo: {persona1.GetType() == persona2.GetType()}"); // False
// Ejemplo práctico: usar en Dictionary
var diccionarioPuntos = new System.Collections.Generic.Dictionary<object, string>();
diccionarioPuntos[punto1] = "Punto inicial";
diccionarioPuntos[punto2] = "Punto final"; // Sobrescribe porque son iguales
Console.WriteLine($"Elementos en diccionario: {diccionarioPuntos.Count}"); // 1
}
}
Tipo dinámico (dynamic)
El tipo dynamic permite que la resolución de tipos se posponga hasta el tiempo de ejecución, proporcionando flexibilidad para interactuar con APIs externas o trabajar con estructuras de datos variables.
Características del tipo dynamic
Característica | Descripción |
---|---|
Resolución de tipos | En tiempo de ejecución |
Verificación de tipos | No se verifica en compilación |
Rendimiento | Menor que tipos estáticos |
Interoperabilidad | Excelente con COM, JSON, etc. |
IntelliSense | Limitado en tiempo de diseño |
Uso básico del tipo dynamic
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Declaración de variables dinámicas
dynamic variable = "Hola Mundo";
Console.WriteLine($"Tipo: {variable.GetType().Name}, Valor: {variable}");
// Cambio de tipo en tiempo de ejecución
variable = 42;
Console.WriteLine($"Tipo: {variable.GetType().Name}, Valor: {variable}");
variable = new DateTime(2024, 12, 25);
Console.WriteLine($"Tipo: {variable.GetType().Name}, Valor: {variable}");
variable = new List<string> { "uno", "dos", "tres" };
Console.WriteLine($"Tipo: {variable.GetType().Name}, Elementos: {variable.Count}");
// Operaciones dinámicas
dynamic numero1 = 10;
dynamic numero2 = 20;
dynamic resultado = numero1 + numero2;
Console.WriteLine($"Suma: {resultado}");
// Concatenación de strings
dynamic texto1 = "Hola ";
dynamic texto2 = "Mundo";
dynamic textoCompleto = texto1 + texto2;
Console.WriteLine($"Concatenación: {textoCompleto}");
// Creación de objetos dinámicos
dynamic persona = new System.Dynamic.ExpandoObject();
persona.Nombre = "Ana";
persona.Edad = 30;
persona.Saludar = new Action<string>(nombre =>
Console.WriteLine($"Hola {nombre}, soy {persona.Nombre}"));
Console.WriteLine($"Persona: {persona.Nombre}, {persona.Edad} años");
persona.Saludar("Carlos");
}
}
ExpandoObject para objetos dinámicos
using System;
using System.Dynamic;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Creación de objeto expandible
dynamic configuracion = new ExpandoObject();
// Añadir propiedades dinámicamente
configuracion.Servidor = "localhost";
configuracion.Puerto = 8080;
configuracion.UsarSSL = true;
configuracion.TimeOut = 30;
// Añadir métodos dinámicamente
configuracion.ObtenerURL = new Func<string>(() =>
{
string protocolo = configuracion.UsarSSL ? "https" : "http";
return $"{protocolo}://{configuracion.Servidor}:{configuracion.Puerto}";
});
configuracion.MostrarConfiguracion = new Action(() =>
{
Console.WriteLine("=== Configuración ===");
foreach (var propiedad in (IDictionary<string, object>)configuracion)
{
if (!(propiedad.Value is Delegate))
{
Console.WriteLine($"{propiedad.Key}: {propiedad.Value}");
}
}
});
// Usar el objeto dinámico
Console.WriteLine($"URL completa: {configuracion.ObtenerURL()}");
configuracion.MostrarConfiguracion();
// Modificar propiedades existentes
configuracion.Puerto = 443;
configuracion.UsarSSL = true;
Console.WriteLine($"\nNueva URL: {configuracion.ObtenerURL()}");
// Añadir propiedades complejas
configuracion.BaseDatos = new ExpandoObject();
configuracion.BaseDatos.Servidor = "db.servidor.com";
configuracion.BaseDatos.Usuario = "admin";
configuracion.BaseDatos.CadenaConexion = new Func<string>(() =>
$"Server={configuracion.BaseDatos.Servidor};User={configuracion.BaseDatos.Usuario}");
Console.WriteLine($"Conexión BD: {configuracion.BaseDatos.CadenaConexion()}");
// Convertir a diccionario para iteración
var diccionario = (IDictionary<string, object>)configuracion;
Console.WriteLine($"\nPropiedades totales: {diccionario.Count}");
// Eliminar propiedades
diccionario.Remove("TimeOut");
Console.WriteLine($"Propiedades después de eliminar: {diccionario.Count}");
}
}
Trabajando con JSON dinámicamente
using System;
using System.Text.Json;
using System.Dynamic;
using System.Collections.Generic;
class Program
{
static void Main()
{
// JSON de ejemplo
string jsonTexto = @"{
""usuario"": {
""id"": 123,
""nombre"": ""Ana García"",
""email"": ""ana@ejemplo.com"",
""activo"": true,
""configuracion"": {
""tema"": ""oscuro"",
""idioma"": ""es"",
""notificaciones"": true
},
""etiquetas"": [""premium"", ""desarrolladora"", ""mentor""]
},
""timestamp"": ""2024-03-15T10:30:00Z"",
""version"": ""1.2.3""
}";
// Parsear JSON a objeto dinámico
dynamic datos = JsonSerializer.Deserialize<object>(jsonTexto);
// Crear una función para convertir JsonElement a dynamic
dynamic ConvertirJsonElement(JsonElement elemento)
{
switch (elemento.ValueKind)
{
case JsonValueKind.Object:
var expandoObj = new ExpandoObject();
var diccionario = (IDictionary<string, object>)expandoObj;
foreach (var propiedad in elemento.EnumerateObject())
{
diccionario[propiedad.Name] = ConvertirJsonElement(propiedad.Value);
}
return expandoObj;
case JsonValueKind.Array:
var lista = new List<dynamic>();
foreach (var item in elemento.EnumerateArray())
{
lista.Add(ConvertirJsonElement(item));
}
return lista;
case JsonValueKind.String:
return elemento.GetString();
case JsonValueKind.Number:
if (elemento.TryGetInt32(out int intValue))
return intValue;
return elemento.GetDouble();
case JsonValueKind.True:
case JsonValueKind.False:
return elemento.GetBoolean();
default:
return null;
}
}
// Convertir el JsonElement a dynamic
if (datos is JsonElement jsonElement)
{
datos = ConvertirJsonElement(jsonElement);
}
// Acceder a los datos dinámicamente
Console.WriteLine("=== Información del Usuario ===");
Console.WriteLine($"ID: {datos.usuario.id}");
Console.WriteLine($"Nombre: {datos.usuario.nombre}");
Console.WriteLine($"Email: {datos.usuario.email}");
Console.WriteLine($"Activo: {datos.usuario.activo}");
Console.WriteLine($"Tema: {datos.usuario.configuracion.tema}");
Console.WriteLine($"Idioma: {datos.usuario.configuracion.idioma}");
// Trabajar con arrays dinámicos
Console.WriteLine("\nEtiquetas:");
foreach (dynamic etiqueta in datos.usuario.etiquetas)
{
Console.WriteLine($"- {etiqueta}");
}
Console.WriteLine($"\nTimestamp: {datos.timestamp}");
Console.WriteLine($"Versión: {datos.version}");
// Modificar datos dinámicamente
datos.usuario.ultimoAcceso = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
datos.usuario.configuracion.modoDesarrollador = true;
// Añadir nuevas propiedades
datos.metadatos = new ExpandoObject();
datos.metadatos.procesado = true;
datos.metadatos.servidor = Environment.MachineName;
Console.WriteLine($"\nÚltimo acceso: {datos.usuario.ultimoAcceso}");
Console.WriteLine($"Modo desarrollador: {datos.usuario.configuracion.modoDesarrollador}");
Console.WriteLine($"Procesado en: {datos.metadatos.servidor}");
}
}
Interoperabilidad COM con dynamic
using System;
class Program
{
static void Main()
{
// Ejemplo conceptual de interoperabilidad COM
// (requeriría referencias COM reales para funcionar)
try
{
// Simulación de uso con Excel (requiere Microsoft.Office.Interop.Excel)
/*
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true;
dynamic workbook = excel.Workbooks.Add();
dynamic worksheet = workbook.ActiveSheet;
// Escribir datos dinámicamente
worksheet.Cells[1, 1] = "Nombre";
worksheet.Cells[1, 2] = "Puntuación";
worksheet.Cells[2, 1] = "Ana";
worksheet.Cells[2, 2] = 95;
worksheet.Cells[3, 1] = "Carlos";
worksheet.Cells[3, 2] = 87;
// Formatear celdas
worksheet.Range["A1:B1"].Font.Bold = true;
worksheet.Columns.AutoFit();
Console.WriteLine("Datos escritos en Excel correctamente");
// Limpiar recursos COM
workbook.Close(false);
excel.Quit();
*/
// Ejemplo alternativo: simulación de API dinámica
dynamic apiResponse = CrearRespuestaAPI();
Console.WriteLine("=== Respuesta de API ===");
Console.WriteLine($"Estado: {apiResponse.status}");
Console.WriteLine($"Mensaje: {apiResponse.message}");
Console.WriteLine($"Total de elementos: {apiResponse.data.total}");
Console.WriteLine("\nElementos:");
foreach (dynamic item in apiResponse.data.items)
{
Console.WriteLine($"- {item.name}: {item.value}");
}
// Acceso dinámico a metadatos
if (apiResponse.metadata != null)
{
Console.WriteLine($"\nVersión API: {apiResponse.metadata.version}");
Console.WriteLine($"Timestamp: {apiResponse.metadata.timestamp}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static dynamic CrearRespuestaAPI()
{
dynamic respuesta = new System.Dynamic.ExpandoObject();
respuesta.status = "success";
respuesta.message = "Datos recuperados correctamente";
respuesta.data = new System.Dynamic.ExpandoObject();
respuesta.data.total = 3;
respuesta.data.items = new System.Collections.Generic.List<dynamic>
{
CrearItem("Producto A", 299.99),
CrearItem("Producto B", 199.50),
CrearItem("Producto C", 89.95)
};
respuesta.metadata = new System.Dynamic.ExpandoObject();
respuesta.metadata.version = "2.1";
respuesta.metadata.timestamp = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");
return respuesta;
}
static dynamic CrearItem(string nombre, double valor)
{
dynamic item = new System.Dynamic.ExpandoObject();
item.name = nombre;
item.value = valor;
item.currency = "EUR";
return item;
}
}
Consideraciones de rendimiento y seguridad
Comparación de rendimiento
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
const int iteraciones = 1000000;
// Prueba con tipos estáticos
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < iteraciones; i++)
{
int a = 10;
int b = 20;
int resultado = a + b;
}
sw.Stop();
Console.WriteLine($"Tipos estáticos: {sw.ElapsedMilliseconds} ms");
// Prueba con tipos dinámicos
sw.Restart();
for (int i = 0; i < iteraciones; i++)
{
dynamic a = 10;
dynamic b = 20;
dynamic resultado = a + b;
}
sw.Stop();
Console.WriteLine($"Tipos dinámicos: {sw.ElapsedMilliseconds} ms");
// Prueba con tipos anónimos
sw.Restart();
for (int i = 0; i < iteraciones; i++)
{
var obj = new { A = 10, B = 20 };
var resultado = obj.A + obj.B;
}
sw.Stop();
Console.WriteLine($"Tipos anónimos: {sw.ElapsedMilliseconds} ms");
}
}
Manejo de errores con dynamic
using System;
using Microsoft.CSharp.RuntimeBinder;
class Program
{
static void Main()
{
dynamic obj = new System.Dynamic.ExpandoObject();
obj.Nombre = "Test";
obj.Valor = 42;
try
{
// Operación válida
Console.WriteLine($"Nombre: {obj.Nombre}");
Console.WriteLine($"Valor: {obj.Valor}");
// Operación que causará error en tiempo de ejecución
Console.WriteLine($"Propiedad inexistente: {obj.PropiedadInexistente}");
}
catch (RuntimeBinderException ex)
{
Console.WriteLine($"Error de binding dinámico: {ex.Message}");
}
try
{
// Método inexistente
obj.MetodoInexistente();
}
catch (RuntimeBinderException ex)
{
Console.WriteLine($"Error de método dinámico: {ex.Message}");
}
// Forma segura de verificar propiedades dinámicas
if (TienePropiedad(obj, "Nombre"))
{
Console.WriteLine($"El objeto tiene la propiedad 'Nombre': {obj.Nombre}");
}
if (!TienePropiedad(obj, "PropiedadInexistente"))
{
Console.WriteLine("El objeto no tiene la propiedad 'PropiedadInexistente'");
}
}
static bool TienePropiedad(dynamic obj, string nombrePropiedad)
{
try
{
var expandoDict = obj as System.Collections.Generic.IDictionary<string, object>;
return expandoDict?.ContainsKey(nombrePropiedad) ?? false;
}
catch
{
return false;
}
}
}
Mejores prácticas y recomendaciones
Cuándo usar cada tipo
using System;
using System.Linq;
using System.Collections.Generic;
public class Producto
{
public int Id { get; set; }
public string Nombre { get; set; }
public decimal Precio { get; set; }
public string Categoria { get; set; }
}
class Program
{
static void Main()
{
var productos = new List<Producto>
{
new Producto { Id = 1, Nombre = "Laptop", Precio = 899m, Categoria = "Tecnología" },
new Producto { Id = 2, Nombre = "Mouse", Precio = 25m, Categoria = "Tecnología" },
new Producto { Id = 3, Nombre = "Libro", Precio = 15m, Categoria = "Educación" }
};
// ✅ BUENO: Usar tipos anónimos para proyecciones LINQ
var resumenProductos = productos.Select(p => new
{
p.Nombre,
p.Precio,
PrecioFormateado = p.Precio.ToString("C"),
EsCaro = p.Precio > 100
}).ToList();
Console.WriteLine("=== Uso correcto de tipos anónimos ===");
foreach (var producto in resumenProductos)
{
Console.WriteLine($"{producto.Nombre}: {producto.PrecioFormateado} {(producto.EsCaro ? "(Caro)" : "")}");
}
// ✅ BUENO: Usar dynamic para interoperabilidad
dynamic configuracionExterna = ObtenerConfiguracionExterna();
Console.WriteLine($"\n=== Configuración externa ===");
Console.WriteLine($"Versión: {configuracionExterna.version}");
// ❌ MALO: No usar dynamic para lógica de negocio normal
// dynamic precio = 100m; // Mejor usar decimal directamente
// ✅ BUENO: Usar tipos anónimos para agrupaciones temporales
var productosAgrupados = productos
.GroupBy(p => p.Categoria)
.Select(g => new
{
Categoria = g.Key,
Cantidad = g.Count(),
PrecioPromedio = g.Average(p => p.Precio),
ProductoMasCaro = g.OrderByDescending(p => p.Precio).First().Nombre
});
Console.WriteLine("\n=== Productos agrupados ===");
foreach (var grupo in productosAgrupados)
{
Console.WriteLine($"{grupo.Categoria}: {grupo.Cantidad} productos");
Console.WriteLine($" Precio promedio: {grupo.PrecioPromedio:C}");
Console.WriteLine($" Más caro: {grupo.ProductoMasCaro}");
}
}
static dynamic ObtenerConfiguracionExterna()
{
// Simula obtener configuración de una fuente externa
dynamic config = new System.Dynamic.ExpandoObject();
config.version = "2.1.0";
config.servidor = "api.ejemplo.com";
config.puerto = 443;
config.funcionalidades = new List<string> { "autenticacion", "cache", "logging" };
return config;
}
}
Resumen
Los tipos anónimos y dinámicos en C# proporcionan flexibilidad adicional para escenarios específicos. Los tipos anónimos son ideales para proyecciones temporales en LINQ, permitiendo crear objetos con propiedades específicas sin definir clases explícitas. Son seguros en tiempo de compilación y eficientes para operaciones de consulta y transformación de datos.
El tipo dynamic ofrece flexibilidad para interoperabilidad con APIs externas, objetos COM y trabajar con estructuras de datos variables como JSON. Sin embargo, sacrifica la seguridad de tipos y el rendimiento, por lo que debe usarse con moderación y en escenarios donde realmente aporte valor.
Recuerda usar tipos anónimos para proyecciones y transformaciones temporales de datos, especialmente con LINQ, y reservar dynamic
para casos donde necesites verdadera flexibilidad de tipos en tiempo de ejecución. Siempre considera las implicaciones de rendimiento y mantén un manejo adecuado de errores cuando trabajes con tipos dinámicos.