Ir al contenido principal

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.