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 |
|---|---|
|
La entrada principal — todo empieza desde aquí |
|
Información sobre un campo |
|
Información sobre una propiedad |
|
Información sobre un método |
|
Información sobre un constructor |
|
Información sobre un evento |
|
“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 | |
Todo lo visible para otros |
| Campos privados | |
Internos ocultos |
| Sólo instancia | |
Sólo no estáticos |
| Sólo static | |
Sólo miembros estáticos |
| Todo a la vez | |
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) | |
|
|
| Propiedad (prop) | |
|
|
| Método (method) | |
|
|
| Constructor | |
|
|
¿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.
GO TO FULL VERSION