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

  1. Qué es la reflexión y para qué sirve
  2. Obtener un Type: typeof y GetType()
  3. Inspeccionar propiedades y métodos: GetProperties() y GetMethods()
  4. Leer y escribir valores de propiedades por reflexión
  5. Crear instancias dinámicamente con Activator.CreateInstance
  6. MostrarPropiedades: un inspector genérico para el dominio de BiblioTech
  7. Coste de la reflexión y casos de uso reales

  1. 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

  1. Obtener un 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 namespace
typeof(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 MaterialBibliotecarioGetType() siempre revela el tipo concreto del objeto en memoria, sin importar el tipo de la variable que lo referencia.

  1. Inspeccionar propiedades y métodos: 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.

  1. 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.

  1. Crear instancias dinámicamente con Activator.CreateInstance

Ademá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.

  1. MostrarPropiedades: un inspector genérico para el dominio de BiblioTech

Uniendo 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 Martinez

MostrarPropiedades 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)"]

  1. 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.Titulo es más rápido, más seguro y más legible que buscarlo por reflexión con una cadena de texto.
  • Olvidar comprobar null en GetProperty/GetMethod: si el nombre buscado no existe (por ejemplo, por una errata en la cadena de texto), estos métodos devuelven null en vez de lanzar una excepción; acceder a .GetValue(...) sobre ese null sin comprobarlo lanza una NullReferenceException.
  • Saltarse el encapsulamiento con BindingFlags.NonPublic sin necesidad real: es técnicamente posible leer o escribir miembros private por reflexión, pero hacerlo rutinariamente rompe las garantías de diseño (como el private set de Disponible) 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

  1. Usando typeof y GetType(), comprueba que typeof(Revista) y new Revista("Muy Interesante", "Redaccion", 350).GetType() representan el mismo Type. Imprime también tipo.Name y tipo.BaseType?.Name para Revista (pista: BaseType devuelve el Type de la clase base, aquí MaterialBibliotecario).

  2. Escribe la función MostrarPropiedades(object obj) de esta lección y utilízala para mostrar las propiedades de un Libro, una Revista y un Socio distintos.

  3. Escribe una función bool TienePropiedad(object obj, string nombrePropiedad) que devuelva true si el tipo de obj tiene una propiedad pública con ese nombre exacto (usando GetProperty y comprobando si el resultado es distinto de null), y false en caso contrario. Pruébala con "Titulo" (debe dar true sobre un Libro) y con "Precio" (debe dar false).

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#

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