CodeGym /Cursos /C# SELF /Reflexión: System.Reflecti...

Reflexión: System.Reflection

C# SELF
Nivel 63, Lección 0
Disponible

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
Type
Representa la descripción de un tipo en .NET (por ejemplo, clase, interfaz, enum, etc.)
PropertyInfo
Propiedad de un tipo
MethodInfo
Método de un tipo
FieldInfo
Campo de un tipo
ConstructorInfo
Constructor de un tipo
Assembly
Representa un ensamblado (EXE o DLL)

Métodos principales de la clase Type

Método Descripción
GetProperties()
Devuelve todas las propiedades públicas
GetFields()
Devuelve todos los campos públicos
GetMethods()
Devuelve todos los métodos públicos
GetConstructors()
Devuelve todos los constructores públicos
GetInterfaces()
Devuelve todas las interfaces que implementa el tipo
GetCustomAttributes()
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.

2
Tarea
C# SELF, nivel 63, lección 0
Bloqueada
Obtener la lista de propiedades, métodos y campos de un tipo
Obtener la lista de propiedades, métodos y campos de un tipo
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION