1. Introducción
La reflexión (del inglés Reflection) es la capacidad de tu programa para inspeccionar su propia estructura en tiempo de ejecución. Imagina que tu programa puede preguntarse: '¿Qué métodos tengo? ¿Qué propiedades tiene este tipo?' y obtener la respuesta en runtime.
Ejemplos de uso de la reflexión en la vida real
- Creación de plugins y módulos: No necesitas saber de antemano con qué clase vas a trabajar — basta con cargar la DLL y descubrir sus métodos al vuelo.
- Automatización de pruebas: Frameworks de testing, por ejemplo xUnit y NUnit, encuentran los métodos de test mediante reflexión.
- Serialización: Con reflexión puedes convertir automáticamente cualquier objeto a JSON/XML sin recorrer manualmente las propiedades.
- Validación de datos por atributos: Por ejemplo, añadiste el atributo [Required] a una propiedad — y quieres validar automáticamente todas esas propiedades.
Por qué esto importa en entrevistas
La habilidad de trabajar con reflexión se pregunta a menudo en entrevistas porque demuestra tu comprensión de cómo funciona .NET y tu capacidad para resolver tareas dinámicas. Si quieres escribir tus propios frameworks o extensiones, la reflexión es indispensable.
2. Namespace y clases base de la reflexión
Toda la funcionalidad de reflexión en C# está disponible a través del namespace System.Reflection. Al inicio del archivo no olvides:
using System.Reflection;
Tipos principales de la reflexión
| Clase/Tipo | Descripción |
|---|---|
|
Representa la descripción de un tipo en .NET (por ejemplo, clase, interfaz, enum, etc.) |
|
Propiedad de un tipo |
|
Método de un tipo |
|
Campo de un tipo |
|
Constructor de un tipo |
|
Representa un ensamblado (EXE o DLL) |
Métodos principales de la clase Type
| Método | Descripción |
|---|---|
|
Devuelve todas las propiedades públicas |
|
Devuelve todos los campos públicos |
|
Devuelve todos los métodos públicos |
|
Devuelve todos los constructores públicos |
|
Devuelve todas las interfaces que implementa el tipo |
|
Devuelve todos los atributos aplicados al tipo |
A menudo a estos métodos se les puede pasar BindingFlags para acceder a miembros privados o estáticos, pero por ahora nos quedaremos con el escenario básico.
3. El primer paso: obtener el objeto Type
Todas las técnicas de reflexión se basan en trabajar con un objeto de tipo Type.
Hay varias formas de obtener este objeto, vamos a ver cada una:
Método 1: A través de un objeto
string text = "Hola, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
Método 2: A través del nombre del tipo
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
Método 3: Por cadena (dinámicamente)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
¡Ten cuidado! Para clases personalizadas en otros ensamblados necesitas especificar el nombre completo del tipo incluyendo el ensamblado.
4. Inspeccionando la estructura del tipo: propiedades, métodos, campos y constructores
Ha llegado el momento de la verdad: obtuviste el objeto Type — y ahora puedes saber todo lo que quieras.
Obtener la lista de propiedades
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
Salida:
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
Obtener la lista de métodos
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
Los tipos grandes pueden tener muchísimos métodos. A menudo no necesitas todos, sino solo los "tuyos" (por ejemplo, excluyendo los métodos heredados de object). Nos ocuparemos de eso más adelante.
Obtener la lista de campos
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
Pero la mayoría de clases normales no tienen campos públicos, ya que la encapsulación es importante :)
Obtener los constructores
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"Constructor: {ctor}");
}
5. Aplicando reflexión en la práctica: analizando nuestra aplicación
Recuerda que en nuestro proyecto de aprendizaje hay una clase TaskItem, donde guardamos tareas. Vamos a inspeccionarla con reflexión.
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
Así puedes conocer todo el "interior" de nuestro tipo en runtime:
Type taskType = typeof(TaskItem);
Console.WriteLine("Propiedades:");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("Campos:");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("Métodos:");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
Prueba a añadir nuevas propiedades a la clase y ver cómo la reflexión las detecta. Así funcionan los serializadores universales e inspectores de objetos.
6. Visualización: cómo está compuesto un objeto Type (esquema)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
Así se ve nuestro "retrato del tipo" — tiene nombre, ensamblado, un conjunto de miembros y atributos.
Obtener información básica del tipo
El objeto Type puede decir qué tipo es:
Type t = typeof(double);
Console.WriteLine(t.Name); // Double
Console.WriteLine(t.FullName); // System.Double
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.IsClass); // False
Console.WriteLine(t.IsValueType); // True
Console.WriteLine(t.IsEnum); // False
Console.WriteLine(t.IsPrimitive); // True
7. Buscar una propiedad, método o campo concreto por nombre
Propiedad
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"Tipo de la propiedad 'Title': {prop.PropertyType}");
}
Método
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"El método 'ToString' devuelve: {method.ReturnType}");
}
Campo
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"Tipo del campo 'IsCompleted': {field.FieldType}");
}
8. Usar reflexión para acceso dinámico a valores
Ahora no solo miramos, sino que también tocamos los valores!
Leer el valor de una propiedad
TaskItem item = new TaskItem { Id = 1, Title = "Probar la reflexión", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"Título: {value}");
}
Establecer el valor de una propiedad
titleProp.SetValue(item, "Título modificado");
Console.WriteLine(item.Title);
Trabajar con campos
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
Para campos y propiedades privados necesitas flags especiales, por ejemplo BindingFlags.NonPublic. Hablaremos de eso más adelante.
9. Invocar métodos mediante reflexión
Puedes invocar métodos incluso sin conocerlos al escribir el código!
TaskItem task = new TaskItem { Title = "¡La reflexión mola!" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
Si el método tiene parámetros, hay que pasarlos como un array de objetos:
public class Calculator
{
public int Add(int a, int b) => a + b;
}
var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8
10. Crear objetos dinámicamente vía constructor
La reflexión permite crear objetos incluso sin el operador new!
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// Es el mismo TaskItem (pero el tipo es object)
Console.WriteLine(newTask.GetType().Name); // TaskItem
Puedes pasar parámetros al constructor:
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Élise");
Console.WriteLine(((User)user).Name); // Élise
11. Errores típicos al trabajar con System.Reflection
Error nº1: Acceder a miembros privados sin BindingFlags.
Por defecto la reflexión solo ve miembros públicos. Sin BindingFlags.NonPublic no puedes acceder a campos o métodos privados.
Error nº2: Ignorar la comprobación de null.
Los métodos GetProperty, GetMethod y otros pueden devolver null si no encuentran el miembro. Sin la comprobación eso llevará a un NullReferenceException.
Error nº3: Elegir mal la sobrecarga del método.
Si hay varias sobrecargas, GetMethod puede devolver la que no esperas. Usa GetMethod especificando los tipos de parámetros.
Error nº4: Usar reflexión en código crítico de rendimiento.
La reflexión es más lenta que las llamadas directas. Para código de alto rendimiento usa cacheo de metadatos o delegates.
GO TO FULL VERSION