La potencia de la Reflexión (Parte 1)

      4 comentarios en La potencia de la Reflexión (Parte 1)

¡Ya estamos de vuelta por estos lares! Hoy vengo a contaros un problema que he tenido en el trabajo y como lo he solucionado con una herramienta no muy visible pero muy potente que nos ofrece .Net, la reflexión. Antes de nada, ¿qué es eso de la reflexión y como puedo usarla en C#?

Si nos fijamos en la definición:

La reflexión proporciona objetos (de tipo Type) que describen los ensamblados, módulos y tipos. Puede usar la reflexión para crear dinámicamente una instancia de un tipo, enlazar el tipo a un objeto existente u obtener el tipo desde un objeto existente e invocar sus métodos, o acceder a sus campos y propiedades. Fuente: Reflexión (C#)

Dicho con otras palabras más sencillas, la reflexión en C# es la capacidad que tenemos para desde el código, conseguir información sobre tipos (clases, librerías, objetos, etc…) y usar esa información para crear objetos dinámicamente y/o acceder a sus miembros sin crear una instancia tipada.

Vale, ¿con lo que he dicho no he conseguido aclararlo mucho más verdad? La verdad es que personalmente creo que es un concepto avanzado del lenguaje y que eso es lo que echa para atrás a muchos programadores. De hecho, si vemos su página de MSDN, vamos que tiene varias opciones:

Seguramente después de esto te sigas preguntando en que me ha podido valer la reflexión de c#. Vale, en mi caso, yo tenia una serie de clases que heredaban todas de una clase base, pero cada una tenia sus particularidades, algo como por ejemplo esto:

public class BaseClass
{
    public string ModeloMotor { get; set; }
}

public class Coche : BaseClass
{
    public bool Descapotable { get; set; }
}

public class Moto : BaseClass
{
    public bool Motor2Tiempos { get; set; }
}

public class Camion : BaseClass
{
    public bool EsVehiculoLargo{ get; set; }
}

Todo ello son posibles clases que recibimos de un método o una api, la cual nos devuelve un «BaseClass», si nos fijamos, todas están relacionadas entre ellas, pero cada una de las clases tiene sus particularidades. Aquí, tendríamos una opción clara, aprovecharnos del poliformismo y tener una clase base que lo contenga todo:

public class BaseClass
{
    public string ModeloMotor { get; set; }
    public bool Descapotable { get; set; }
    public bool Motor2Tiempos { get; set; }
    public bool EsVehiculoLargo{ get; set; }
}

public class Coche : BaseClass
{
    //...
}

public class Moto : BaseClass
{
    //...
}

public class Camion : BaseClass
{
    //...
}

De este modo, desde cada una de las clases hijas tendremos acceso a todas las propiedades. Pero claro, tampoco tiene mucho sentido hablar de un camión con un motor de dos tiempos o una moto que sea un vehículo largo.

Otra opción posible, es hacer casting a absolutamente todas las posibles clases hijas para ver si alguna coincide:

var moto = claseBase as Moto;
if (!(moto is null))
    Console.WriteLine(moto.Motor2Tiempos);
var coche = claseBase as Coche;
if (!(coche is null))
    Console.WriteLine(coche.Descapotable);
var camion = claseBase as Camion;
if (!(camion is null))
    Console.WriteLine(camion.Tara);
//.....

¿Os imagináis lo larga que se puede hacer la lista si tengo 20 clases diferente que heredan de clase base?

Aquí es donde entra en juego la reflexión, desde un objeto de tipo «BaseClass», mediante la reflexión de C#, podemos iterar las propiedades del objeto encapsulado y obtener su valor, por ejemplo, vamos a crear un método de extensión que nos permita obtener desde la clase base si es un vehículo largo:

static class Extensiones
{
    public static bool? EsVehiculoLargo(this BaseClass clase)
    {
        //Obtenemos todas las propiedades de la clase que nos pasan
        var properties = clase.GetType().GetProperties();
        //Iteramos las propiedades
        foreach (var propertyInfo in properties)
        {
            //Si alguna se llama 
            if (propertyInfo.Name == "EsVehiculoLargo")
            {
                //Retornamos el valor
                return Convert.ToBoolean(propertyInfo.GetValue(clase));
            }
        }
        //Si ninguna coincide, retornamos null
        return null;
    }
}

Si nos fijamos en el código, estamos creando un método extensor para «BaseClass». En él, lo que vamos a hacer es obtener todas las propiedades, pero no de «BaseClass», sino de la clase hija encapsulada dentro. Dentro de las propiedades, vamos a buscar la que tenga el nombre que nos interesa, y si hay alguna, vamos a obtener el valor pasándole al objeto PropertyInfo la instancia de donde tiene que obtener el valor.

Con esto, no solo podemos leer la propiedad, también podríamos escribirla llamando al método «SetValue»:

propertyInfo.SetValue(clase, true);
propertyInfo.SetValue(clase, false);

Pero eso no es todo lo que podemos conseguir, también podemos obtener información sobre su tipo, obtener el método get o set, los atributos que tiene, si es de lectura y/o escritura …

De este modo tan elegante, podemos acceder a la información de la clase hija sin tener que conocer el tipo exacto de la clase hija. En mi caso, pude conseguir consumir la api sin tener que hacer casting individuales (en mi caso yo tenía más de 60 clases hijas).

Pero no se queda ahí, ¡la reflexión da para mucho más! Si con este ejemplo te ha picado el gusanillo sobre lo que puede ofrecer la reflexión, en las próximas entradas vamos a profundizar en diferentes casos, como crear un objeto de una clase de manera dinámica, buscar entre los atributos de un objeto, o llamar a métodos de objetos dinámicamente. De momento, dejo un enlace al código para poder probarlo y lo iremos ampliando.

4 pensamientos en “La potencia de la Reflexión (Parte 1)

  1. Joaquin

    Creo que llegar a una jerarquía de herencia de 60 clases es un poco hardcore… Parece que habría que ir dividiendo responsabilidades. Aún así buen post sobre reflexión en C#! 👍

    Responder
    1. JorTurFer Autor

      Buenas Joaquín, gracias por el comentario!!
      Totalmente de acuerdo, pero al final eran clases que contenían particularidades para distintos deserializadores binarios, y el cliente había fundamentado así su desarrollo antes de entrar nosotros. Para un inicio
      seguro que le fue bien, pero ahora es un monstruo que hay que tocar con cuidado.

      Responder
  2. JorTurFer Autor

    En eso tienes toda la razón, pero si no hubiese visto ese monstruo no me habría aventurado a escribir sobre la reflexión 😁. Es un tema que me parece super potente, que la gente evita por desconocimiento o miedo, y que puede ahorrar muchos quebraderos de cabeza. De momento como mínimo voy a hacer dos entradas, y ya veremos si se convierte en una serie más del blog… (ya puestos, nos metemos bien al fondo de este mundo 😋)

    Responder

Deja un comentario