Hasta ahora, todo el código de BiblioTech ha conocido en tiempo de compilación qué
propiedades y métodos tiene cada clase: al escribir libro1.Titulo, el compilador comprueba
que Libro tiene una propiedad Titulo antes de generar el programa. La reflexión
(System.Reflection) invierte esa idea: permite que un programa inspeccione y manipule sus
propios tipos en tiempo de ejecución, sin conocerlos de antemano. Es, literalmente, el
mecanismo que hace posible que JsonSerializer (Módulo 5) sepa qué propiedades tiene un
Libro sin que nadie haya escrito código específico de mapeo para esa clase: por debajo,
recorre sus propiedades por reflexión. Esta lección abre el Módulo 6 explicando cómo funciona
ese mecanismo y cómo usarlo directamente.
Contenido
- Qué es la reflexión y para qué sirve
- Obtener un
Type:typeofyGetType() - Inspeccionar propiedades y métodos:
GetProperties()yGetMethods() - Leer y escribir valores de propiedades por reflexión
- Crear instancias dinámicamente con
Activator.CreateInstance MostrarPropiedades: un inspector genérico para el dominio de BiblioTech- Coste de la reflexión y casos de uso reales
- Qué es la reflexión y para qué sirve
La reflexión es la capacidad de un programa de examinar su propia estructura —clases,
propiedades, métodos, atributos— como si fueran datos, y de actuar sobre ella sin que el
código que la usa conozca de antemano el tipo concreto con el que trabaja. Todo el
namespace System.Reflection gira en torno a un tipo central: System.Type, que representa,
en tiempo de ejecución, "la descripción completa de una clase" (sus propiedades, sus métodos,
su clase base, sus interfaces...).
La reflexión no es una herramienta que se use a diario en el código de dominio de una
aplicación como BiblioTech (Libro.Titulo se sigue escribiendo directamente, sin reflexión, en
el 99% del código). Su valor aparece en código genérico e infraestructural: frameworks que
necesitan trabajar con "cualquier tipo" sin conocerlo por adelantado. Ya has usado varias
piezas construidas así sin saberlo:
| Herramienta ya vista en el curso | Qué hace por debajo con reflexión |
|---|---|
JsonSerializer.Serialize/Deserialize (Módulo 5) |
Recorre las propiedades públicas de un tipo para leerlas o asignarlas |
Entity Framework Core (DbSet<T>) |
Inspecciona las propiedades de Libro, Socio... para mapearlas a columnas |
| Frameworks de inyección de dependencias (Módulo 8) | Crean instancias de clases cuyo tipo concreto no conocen en tiempo de compilación |
- Obtener un
Type: typeof y GetType()
Type: typeof y GetType()Hay dos formas de obtener el Type que representa una clase, según se conozca el tipo en
tiempo de compilación o solo se tenga una instancia en tiempo de ejecución:
using System;
// Forma 1: typeof, cuando el tipo se conoce en tiempo de compilacion
Type tipoLibro = typeof(Libro);
// Forma 2: GetType(), cuando solo se tiene una instancia (el tipo puede no conocerse de antemano)
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Type tipoDesdeInstancia = libro1.GetType();
Console.WriteLine(tipoLibro == tipoDesdeInstancia); // True: ambos representan el mismo Type
Console.WriteLine(tipoLibro.Name); // "Libro"
Console.WriteLine(tipoLibro.FullName); // el nombre completo, con namespacetypeof(T) |
objeto.GetType() |
|
|---|---|---|
| Cuándo se usa | El tipo es conocido en el código, escrito literalmente | Solo se dispone de una referencia en tiempo de ejecución |
| Resultado con polimorfismo | El tipo exacto indicado entre paréntesis | El tipo real del objeto, aunque la variable sea de un tipo más general |
El segundo matiz es importante: si MaterialBibliotecario material = libro1; (recordando el
Polimorfismo del Módulo 3), material.GetType() devuelve Libro, no MaterialBibliotecario
—GetType() siempre revela el tipo concreto del objeto en memoria, sin importar el tipo de
la variable que lo referencia.
- Inspeccionar propiedades y métodos:
GetProperties() y GetMethods()
GetProperties() y GetMethods()Un Type expone, entre muchos otros, los métodos GetProperties() y GetMethods(), que
devuelven arrays de PropertyInfo y MethodInfo respectivamente: objetos que describen, uno
por uno, cada propiedad o método público del tipo.
using System.Reflection;
Type tipoLibro = typeof(Libro);
PropertyInfo[] propiedades = tipoLibro.GetProperties();
foreach (PropertyInfo propiedad in propiedades)
{
Console.WriteLine($"Propiedad: {propiedad.Name} (tipo {propiedad.PropertyType.Name})");
}
// Propiedad: Titulo (tipo String)
// Propiedad: Autor (tipo String)
// Propiedad: Disponible (tipo Boolean)
// Propiedad: Isbn (tipo String)
MethodInfo[] metodos = tipoLibro.GetMethods();
foreach (MethodInfo metodo in metodos)
{
Console.WriteLine($"Metodo: {metodo.Name}");
}
// Metodo: Prestar, Devolver, MostrarFicha, Describir, Coincide, GetType, ToString... (heredados incluidos)GetProperties() sobre typeof(Libro) incluye tanto las propiedades declaradas directamente
en Libro (Isbn) como las heredadas de MaterialBibliotecario (Titulo, Autor,
Disponible): la reflexión respeta la jerarquía de herencia igual que el propio lenguaje.
GetMethods(), por defecto, también incluye métodos heredados de object (ToString(),
GetType(), Equals()...), lo cual conviene tener presente al recorrer el resultado.
- Leer y escribir valores de propiedades por reflexión
Un PropertyInfo no es solo una descripción: también permite leer (GetValue) o
escribir (SetValue) el valor de esa propiedad sobre una instancia concreta, pasada como
argumento:
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
PropertyInfo? propiedadTitulo = typeof(Libro).GetProperty("Titulo");
object? valorTitulo = propiedadTitulo?.GetValue(libro1);
Console.WriteLine(valorTitulo); // "Rayuela"
propiedadTitulo?.SetValue(libro1, "Rayuela (edicion revisada)");
Console.WriteLine(libro1.Titulo); // "Rayuela (edicion revisada)"GetProperty("Titulo") busca, por su nombre como cadena de texto, la propiedad concreta
Titulo; devuelve null si no existe ninguna con ese nombre exacto, de ahí el ? en
GetValue. Esto es justamente lo que distingue a la reflexión de acceder a libro1.Titulo
directamente: el nombre de la propiedad puede ser un dato (una cadena que llega desde
configuración, desde un fichero, o que se calcula en un bucle), no un identificador fijo escrito
en el código.
Disponible, con su private set (Módulo 3), sigue respetando su encapsulamiento frente a
SetValue por defecto: GetProperty("Disponible")?.SetValue(libro1, false) lanza una
excepción, salvo que se pida explícitamente acceso a miembros no públicos con
BindingFlags.NonPublic, una posibilidad que existe pero que conviene evitar —saltarse un
private set por reflexión rompe precisamente la garantía que ese private set pretendía dar.
- Crear instancias dinámicamente con
Activator.CreateInstance
Activator.CreateInstanceAdemás de inspeccionar tipos ya instanciados, la reflexión permite crear instancias nuevas
de un tipo que solo se conoce en tiempo de ejecución (por ejemplo, a partir de su nombre como
cadena de texto), con Activator.CreateInstance:
using System;
Type tipoSocio = typeof(Socio);
// Activator.CreateInstance necesita los argumentos exactos de algun constructor de Socio
object? nuevoSocio = Activator.CreateInstance(tipoSocio, 99, "Socio Creado Dinamicamente");
if (nuevoSocio is Socio socio)
{
Console.WriteLine($"{socio.Id}: {socio.Nombre}"); // 99: Socio Creado Dinamicamente
}Activator.CreateInstance(tipo, argumentos...) busca, entre los constructores públicos de
tipo, uno cuya firma coincida con los argumentos indicados, y lo invoca; el resultado se
devuelve como object, por lo que hace falta un is/as (Módulo 3) para volver a tratarlo
como Socio con todos sus miembros. Este mecanismo es la base de muchos frameworks: un
contenedor de inyección de dependencias (mencionado de pasada aquí; se retoma en el Módulo 8)
recibe, en tiempo de ejecución, una lista de tipos a instanciar sin conocerlos en tiempo de
compilación, y usa Activator.CreateInstance (o mecanismos equivalentes más optimizados) para
construir cada uno.
MostrarPropiedades: un inspector genérico para el dominio de BiblioTech
MostrarPropiedades: un inspector genérico para el dominio de BiblioTechUniendo GetType(), GetProperties() y GetValue(), se puede escribir una única función que
imprime el nombre y el valor de cada propiedad pública de cualquier objeto, sin necesidad
de un método MostrarFicha() distinto para cada clase del dominio:
using System.Reflection;
static void MostrarPropiedades(object obj)
{
Type tipo = obj.GetType();
Console.WriteLine($"--- {tipo.Name} ---");
foreach (PropertyInfo propiedad in tipo.GetProperties())
{
object? valor = propiedad.GetValue(obj);
Console.WriteLine($"{propiedad.Name}: {valor}");
}
}Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Socio socio1 = new Socio(1, "Ana Martinez");
MostrarPropiedades(libro1);
// --- Libro ---
// Titulo: Rayuela
// Autor: Julio Cortazar
// Disponible: True
// Isbn: 978-84-376-0495-4
MostrarPropiedades(socio1);
// --- Socio ---
// Id: 1
// Nombre: Ana MartinezMostrarPropiedades recibe object obj (el tipo más general posible, Módulo 3) precisamente
porque no necesita saber de antemano si recibirá un Libro, una Revista o un Socio: usa
obj.GetType() para descubrirlo en tiempo de ejecución, y a partir de ahí recorre sus
propiedades de forma genérica. Es una herramienta de depuración muy práctica: un único método
sirve para inspeccionar el estado interno de cualquier objeto del dominio, sin escribir ni
mantener un MostrarFicha() a medida por cada clase nueva que se añada a BiblioTech.
flowchart LR
A["MostrarPropiedades(obj)"] --> B["obj.GetType()"]
B --> C["tipo.GetProperties()"]
C --> D["Por cada PropertyInfo: propiedad.GetValue(obj)"]
D --> E["Console.WriteLine(nombre + valor)"]
- Coste de la reflexión y casos de uso reales
La reflexión tiene un coste de rendimiento notable frente al acceso directo (libro1.Titulo):
localizar un miembro por su nombre, comprobar tipos y firmas en tiempo de ejecución es
sensiblemente más lento que una llamada resuelta en tiempo de compilación. Esto no significa
que deba evitarse siempre, sino que hay que reservarla para donde de verdad aporta valor:
| Caso de uso | Por qué encaja con reflexión |
|---|---|
Serializadores genéricos (JsonSerializer) |
Necesitan funcionar con cualquier clase, sin código específico por tipo |
| Frameworks de inyección de dependencias (Módulo 8) | Crean instancias de tipos configurados externamente, no conocidos en tiempo de compilación |
Herramientas de depuración/inspección (MostrarPropiedades) |
El beneficio de la inspección genérica compensa el coste, porque no se ejecutan en el camino crítico de rendimiento |
Código de dominio cotidiano (libro1.Prestar()) |
No encaja: el acceso directo es más rápido, más seguro en tiempo de compilación y más legible |
Errores Comunes y Consejos
- Usar reflexión donde el acceso directo ya funciona: si el tipo se conoce en tiempo de
compilación (el caso habitual en el código de dominio de BiblioTech), acceder directamente a
libro1.Tituloes más rápido, más seguro y más legible que buscarlo por reflexión con una cadena de texto. - Olvidar comprobar
nullenGetProperty/GetMethod: si el nombre buscado no existe (por ejemplo, por una errata en la cadena de texto), estos métodos devuelvennullen vez de lanzar una excepción; acceder a.GetValue(...)sobre esenullsin comprobarlo lanza unaNullReferenceException. - Saltarse el encapsulamiento con
BindingFlags.NonPublicsin necesidad real: es técnicamente posible leer o escribir miembrosprivatepor reflexión, pero hacerlo rutinariamente rompe las garantías de diseño (como elprivate setdeDisponible) que el propio código estableció a propósito. - No medir el impacto en rendimiento antes de usar reflexión en un camino crítico: en un
bucle que se ejecuta millones de veces, la diferencia entre acceso directo y reflexión puede
ser significativa; para código de infraestructura muy sensible al rendimiento existen
alternativas más avanzadas (compilación de expresiones,
Source Generators) fuera del alcance de este curso. - Consejo: antes de escribir código con reflexión, pregúntate si el problema se puede resolver con las herramientas ya vistas (interfaces, genéricos, polimorfismo); la reflexión es la herramienta correcta cuando el tipo concreto no se conoce hasta tiempo de ejecución, no un sustituto general de un buen diseño orientado a objetos.
Ejercicios
-
Usando
typeofyGetType(), comprueba quetypeof(Revista)ynew Revista("Muy Interesante", "Redaccion", 350).GetType()representan el mismoType. Imprime tambiéntipo.Nameytipo.BaseType?.NameparaRevista(pista:BaseTypedevuelve elTypede la clase base, aquíMaterialBibliotecario). -
Escribe la función
MostrarPropiedades(object obj)de esta lección y utilízala para mostrar las propiedades de unLibro, unaRevistay unSociodistintos. -
Escribe una función
bool TienePropiedad(object obj, string nombrePropiedad)que devuelvatruesi el tipo deobjtiene una propiedad pública con ese nombre exacto (usandoGetPropertyy comprobando si el resultado es distinto denull), yfalseen caso contrario. Pruébala con"Titulo"(debe dartruesobre unLibro) y con"Precio"(debe darfalse).
Soluciones
Type tipoPorTypeof = typeof(Revista);
Revista revista1 = new Revista("Muy Interesante", "Redaccion", 350);
Type tipoPorInstancia = revista1.GetType();
Console.WriteLine(tipoPorTypeof == tipoPorInstancia); // True
Console.WriteLine(tipoPorTypeof.Name); // "Revista"
Console.WriteLine(tipoPorTypeof.BaseType?.Name); // "MaterialBibliotecario"
static void MostrarPropiedades(object obj)
{
Type tipo = obj.GetType();
Console.WriteLine($"--- {tipo.Name} ---");
foreach (PropertyInfo propiedad in tipo.GetProperties())
{
Console.WriteLine($"{propiedad.Name}: {propiedad.GetValue(obj)}");
}
}
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Revista revista1 = new Revista("Muy Interesante", "Redaccion", 350);
Socio socio1 = new Socio(1, "Ana Martinez");
MostrarPropiedades(libro1);
MostrarPropiedades(revista1);
MostrarPropiedades(socio1);
static bool TienePropiedad(object obj, string nombrePropiedad)
{
return obj.GetType().GetProperty(nombrePropiedad) != null;
}
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(TienePropiedad(libro1, "Titulo")); // True
Console.WriteLine(TienePropiedad(libro1, "Precio")); // False
Conclusión
En esta lección has conocido la reflexión: cómo obtener el Type de una clase con typeof o
GetType(), cómo inspeccionar sus propiedades y métodos con GetProperties()/GetMethods(),
cómo leer y escribir valores por reflexión con GetValue()/SetValue(), y cómo crear
instancias dinámicamente con Activator.CreateInstance. También has visto que la reflexión es
precisamente el mecanismo que hace posible herramientas ya usadas en el curso, como
JsonSerializer o Entity Framework Core, y por qué conviene reservarla para código genérico de
infraestructura en vez de para el día a día del dominio.
La siguiente lección, Atributos, retoma algo que ya has visto de pasada sin detenerte en ello:
[JsonPropertyName] y [JsonPolymorphic] de la lección de Serialización son, en realidad,
atributos, una forma de anotar código con metadatos adicionales. Y la reflexión que acabas
de aprender es exactamente la herramienta que permite leer esos atributos en tiempo de
ejecución para tomar decisiones —la pieza que faltaba para entender cómo funcionan de verdad
por debajo.
Curso de Programación en C#
Módulo 1: Introducción a C#
- Introducción a C#
- Configuración del Entorno de Desarrollo
- Programa Hola Mundo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Arrays y Cadenas de Texto
Módulo 2: Estructuras de Control
Módulo 3: Programación Orientada a Objetos
- Clases y Objetos
- Métodos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- Structs y Records: Tipos por Valor y por Referencia
Módulo 4: Conceptos Avanzados de C#
- Interfaces
- Delegados y Eventos
- Pattern Matching y Características Modernas de C#
- Genéricos
- Colecciones
- LINQ (Consulta Integrada en el Lenguaje)
- Programación Asíncrona
Módulo 5: Trabajando con Datos
- Entrada/Salida de Archivos
- Serialización
- Conectividad con Bases de Datos
- Entity Framework
- Trabajo con JSON y Consumo de APIs REST
Módulo 6: Temas Avanzados
- Reflexión
- Atributos
- Programación Dinámica
- Gestión de Memoria y Recolección de Basura
- Multihilo y Programación Paralela
Módulo 7: Construcción de Aplicaciones
- Formularios de Windows
- WPF (Windows Presentation Foundation)
- ASP.NET Core
- Blazor
- Xamarin y .NET MAUI
Módulo 8: Mejores Prácticas y Patrones de Diseño
- Estándares de Codificación y Mejores Prácticas
- Patrones de Diseño
- Inyección de Dependencias e Inversión de Control
- Pruebas Unitarias
- Revisión y Refactorización de Código
