CodeGym /Cursos /C# SELF /Uso de la reflexión

Uso de la reflexión

C# SELF
Nivel 63, Lección 1
Disponible

1. Introducción

En C# cualquier clase (o struct) es un conjunto de miembros. Entre ellos están:

  • Campos (fields): variables que almacenan datos.
  • Propiedades (properties): wrappers “inteligentes” con get/set.
  • Métodos (methods): funcionalidad.
  • Constructores (constructors): maneras de crear una instancia.
  • Eventos (events): publicación-suscripción de forma elegante.

Imagínate que tienes una lupa mágica con la que puedes mirar dentro de cualquier tipo, ver qué contiene e incluso cambiarlo. Eso es la reflexión.

Espacio de nombres y tipos principales de la reflexión

Antes de empezar, no olvides importar el espacio de nombres:

using System.Reflection;

Clases e interfaces clave:

Clase/interfaz Descripción
Type
La entrada principal — todo empieza desde aquí
FieldInfo
Información sobre un campo
PropertyInfo
Información sobre una propiedad
MethodInfo
Información sobre un método
ConstructorInfo
Información sobre un constructor
EventInfo
Información sobre un evento
MemberInfo
“Padre” común para todo lo anterior

2. Obteniendo miembros de un tipo mediante reflexión

Obtenemos campos y propiedades

Ejemplo: enumerar campos y propiedades

Supongamos que tenemos esta clase:

public class Person
{
    public string Name { get; set; }
    public int Age;
    private string Secret = "Secret word";
}

Obtenemos campos:

Type personType = typeof(Person);

FieldInfo[] fields = personType.GetFields(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var field in fields)
{
    Console.WriteLine($"Campo: {field.Name}, Tipo: {field.FieldType.Name}, Acceso: {field.Attributes}");
}

Obtenemos propiedades:

PropertyInfo[] properties = personType.GetProperties(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var property in properties)
{
    Console.WriteLine($"Propiedad: {property.Name}, Tipo: {property.PropertyType.Name}");
}

Explicaciones

  • BindingFlags — es un conjunto de “flags” que indican qué miembros buscar (public/private, instance/static, etc.).
  • Campos y propiedades son diferentes: campo — un trozo de datos; propiedad — métodos get/set que ocultan el campo.

Obtenemos métodos

MethodInfo[] methods = personType.GetMethods(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var method in methods)
{
    Console.WriteLine($"Método: {method.Name}");
}

Te sorprenderá cuántos métodos aparecen. Verás getters/setters de propiedades y métodos de servicio heredados de object.

Filtro útil: para obtener solo tus métodos “explícitos”, puedes filtrar por nombre o por atributo.

Qué buscar con qué BindingFlags

Qué buscamos Ejemplo de llamada Descripción
Campos públicos
GetFields(BindingFlags.Public | ...)
Todo lo visible para otros
Campos privados
GetFields(BindingFlags.NonPublic | ...)
Internos ocultos
Sólo instancia
BindingFlags.Instance
Sólo no estáticos
Sólo static
BindingFlags.Static
Sólo miembros estáticos
Todo a la vez
BindingFlags.Public | NonPublic | Instance | Static
Lista completa

3. Creación dinámica de una instancia de tipo

¿Por qué mola esto?
Puedes crear un objeto sin conocer su tipo exacto en tiempo de compilación.

Manera sencilla — mediante Activator.CreateInstance

// Obtenemos el Type de alguna forma, por ejemplo:
Type myType = typeof(Person);
// Creamos el objeto:
object obj = Activator.CreateInstance(myType);
// Ahora obj es una instancia de Person, pero como object

Si la clase no tiene constructor sin parámetros, puedes pasar parámetros:

public class Animal
{
    public string Name { get; }
    public Animal(string name) { Name = name; }
}

// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "Rover");

¡Importante!

Console.WriteLine(dog.GetType().Name); // Animal

Forma más personalizada — usando ConstructorInfo

ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "Rover" });

En la práctica normalmente se usa Activator, pero a veces necesitas parámetros exactos o trabajar con constructores privados — ahí ConstructorInfo ayuda.

4. Acceso a campos y propiedades por nombre

Obtener y cambiar un campo

Person p = new Person();
Type t = p.GetType();

FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42

Trabajar con campos privados:

FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
    secret.SetValue(p, "Oh, secret is revealed");
    Console.WriteLine(secret.GetValue(p));
}
else
{
    Console.WriteLine("Campo Secret no encontrado");
}

¡Cuidado! No abuses del acceso a campos privados — es romper la encapsulación y puede crear bugs potenciales.

Obtener y cambiar propiedades

PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "Bill");
Console.WriteLine(pi.GetValue(p)); // Bill

Las propiedades no son campos; son métodos get/set detrás de escena.

¿Por qué a veces no funciona?

Si tu campo o propiedad es privada, asegúrate de añadir los BindingFlags adecuados (por ejemplo, NonPublic). Si la propiedad no tiene setter, no podrás escribirle mediante reflexión — esa regla se mantiene.

5. Llamada a métodos por nombre

Llamada a método simple sin parámetros

public class Greeter
{
    public void SayHello()
    {
        Console.WriteLine("Hello!");
    }
}

Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // Hello!

Llamada a método con parámetros

public class MathOp
{
    public int Add(int x, int y) => x + y;
}

MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12

Si el método es estático, pasa null como primer argumento de Invoke.

Trabajo con métodos sobrecargados

MethodInfo mi = t.GetMethod(
    "Add", 
    new Type[] { typeof(int), typeof(int) }
);

O recórrelo y filtra manualmente:

foreach(var method in t.GetMethods()) {
    if (method.Name == "Add" && method.GetParameters().Length == 2) 
    {
        // ... ¡nuestro método!
    }
}

6. Detalles útiles

Miembros principales, métodos y propiedades para analizar un tipo

Miembro de la clase Método para obtener info Clase de resultado Cómo leer/modificar valor
Campo (field)
GetField, GetFields
FieldInfo
.GetValue(obj), .SetValue(obj, x)
Propiedad (prop)
GetProperty, GetProperties
PropertyInfo
.GetValue(obj), .SetValue(obj, x)
Método (method)
GetMethod, GetMethods
MethodInfo
.Invoke(obj, parametros)
Constructor
GetConstructor, GetConstructors
ConstructorInfo
.Invoke(parametros)

¿Para qué sirve todo esto en la vida real?

  • En frameworks ORM (por ejemplo, Entity Framework construye consultas SQL a partir de tus modelos).
  • En sistemas de serialización y parsers (por ejemplo, serializadores JSON).
  • En librerías genéricas para logging, testing, generadores de UI.
  • En entrevistas — suelen preguntar “¿cómo escribirías un clonador genérico de objetos?”.
  • Para escribir plugins, cuando tu programa carga y ejecuta código escrito por otros.

Aun si ahora te parece magia de películas de hackers, créeme — te será útil.

7. Errores típicos y particularidades

Los desarrolladores a menudo se confunden con BindingFlags — no ven campos privados o, al contrario, obtienen demasiados métodos. Revisa siempre la combinación de flags: para campos privados usa NonPublic, para estáticos — Static, etc.

Al invocar métodos mediante Invoke pasa los argumentos en el mismo orden y tipo que en la declaración. Un orden/tipo incorrecto causará TargetParameterCountException o ArgumentException. El resultado viene como object — no olvides castear (o usar pattern matching).

No olvides el rendimiento: la reflexión es más lenta que las llamadas directas. En código caliente cachea los Type/MethodInfo/PropertyInfo encontrados o genera delegados por adelantado.

Verás métodos de soporte como get_PropertyName y set_PropertyName. Son métodos de acceso a propiedades — no necesitas crearlos a mano.

Si modificas campos o propiedades privadas, estás rompiendo la encapsulación. Un uso incorrecto puede provocar bugs difíciles de detectar. Como decía el tío Ben: “Con gran poder viene gran responsabilidad” — úsalo con cuidado, especialmente en proyectos de librería.

2
Tarea
C# SELF, nivel 63, lección 1
Bloqueada
Llamada a un método usando reflexión
Llamada a un método usando reflexión
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION