Day: 17 mayo 2009

Introducción a Reflection: Recorriendo los elementos de un objeto


La reflexión es un proceso por el cual se posibilita observar y modificar la estructura y comportamiento de un objeto. Utilizar reflexión para programar implica un grado de abstracción enorme, y su complejidad es relativamente elevada.
La reflexión es soportada por multitud de lenguajes, y se aprovecha de la posibilidad de tratar a las instrucciones como datos. Generalmente se utiliza con el objetivo de observar o modificar el estado o el comportamiento de un programa en tiempo de ejecución. Es, por tanto, la base de la metaprogramación.
El Framework .NET posee un espacio de nombres dedicado a esta filosofía: System.Reflection. Veremos un pequeño ejemplo de cómo podemos, por ejemplo, obtener información sobre un objeto en concreto, como el nombre de sus elementos y su contenido.

La clase Dictionary

Las colecciones de datos, incluidas en los espacios de nombres System.Collections y System.Collections.Generic nos permiten agrupar datos de forma más eficiente de lo que nos permitiría, por ejemplo, un Array.
Las estructuras de este espacio de nombres más utilizadas suelen ser los Hashtable (pares clave-valor) y los ArrayList (Arrays de objetos heterogéneos). No obstante, y a nivel personal, una de las colecciones de datos que más potentes me parecen es la clase Dictionary.
Esta clase, al igual que el Hashtable, permite almacenar pares clave-valor. La diferencia está en que, a diferencia del Hashtable, la clave puede ser del tipo que nosotros escojamos (un entero, una estructura, o incluso una clase). Además, es posible acceder tanto a la lista de claves como a la de valores. Por tanto, al crear un diccionario le proporcionaremos la información del tipo de la clave y el tipo del valor.
Para nuestro ejemplo, crearemos un diccionario de tipo (string, object), es decir, un diccionario en el que la clave sea una cadena de texto y el valor sea un objeto.

Obteniendo información sobre un objeto.

Conociendo la clase Dictionary, es hora de declarar el cuerpo de la función que vamos a codificar. Se tratará de una función que recibirá dos parámetros: un objeto y un MemberType. El MemberType no es más que el tipo de miembro que queremos obtener del objeto (método, propiedad, etc.). Por lo tanto, el cuerpo de nuestra función será el siguiente:

private Dictionary<string , object> ObtenerElementos(object objeto, MemberTypes TipoElemento)
{
}

No debemos olvidarnos, eso sí, de incluir los espacios de nombres System.Reflection y System.Collections.Generic para poder hacer uso de la clase Dictionary y de Reflection, respectivamente.
Comenzaremos por declarar un diccionario de tipo (string, object).

Dictionary<string , object> Elementos = new Dictionary<string , object>();

A continuación, recorreremos la información de cada elemento del objeto con un bucle foreach. Para ello utilizaremos la clase MemberInfo.

foreach (MemberInfo infoMiembro in objeto.GetType().GetMembers())
{
}

En el interior del bucle, comprobaremos si el tipo del objeto es el que nosotros buscamos, y si es así, lo insertaremos en nuestro diccionario.

if (infoMiembro.MemberType == TipoElemento)
{
   if ((PropertyInfo)infoMiembro != null)
     Elementos.Add(((PropertyInfo)infoMiembro).Name, ((PropertyInfo)infoMiembro).GetValue(objeto, null));
}

Como podemos ver, el tipo de miembro puede ser de varios tipos.

090509MemberTypes
Finalmente, el diccionario devolverá los nombres de los elementos junto a sus respectivos valores. Por ejemplo, si buscamos las propiedades del objeto, le pasaríamos a la función como segundo parámetro MemberTypes.Property. Si el objeto tuviera una propiedad llamada “IdUsuario” cuyo valor fuese 1, el diccionario guardaría una entrada (“IdUsuario”, (object)1).

El método completo sería el siguiente:

 /// <summary>
 /// Función encargada de devolver un tipo de elemento en concreto de un objeto en un conjunto
 /// de pares clave - valor.
 /// </summary>
 /// <changelog>
 /// Daniel García    15/05/2009    Creación
 /// </changelog>
 private Dictionary<string , object> ObtenerElementos(object objeto, MemberTypes TipoElemento)
 {
   try
   {
     // Declaramos un Diccionario que contendra el nombre de los elementos del objeto y el
     //contenido de cada elemento.
     Dictionary<string , object> Elementos = new Dictionary<string , object>();

     // Se recorren los miembros del objeto
     foreach (MemberInfo infoMiembro in objeto.GetType().GetMembers())
     {
       // Si el tipo del objeto es del tipo que buscamos, se añade al diccionario
       if (infoMiembro.MemberType == TipoElemento)
       {
         if ((PropertyInfo)infoMiembro != null)
           Elementos.Add(((PropertyInfo)infoMiembro).Name, ((PropertyInfo)infoMiembro).GetValue(objeto, null));
       }
     }
     return Elementos;
   }
   catch (Exception ex)
   {
   throw (ex);
   }
 }

Posteriormente encontraremos una utilidad práctica a esta función, aplicándola a nuestro patrón DAO para no tener que depender de los nombres de los campos. Esta sencilla función ayudará a aumentar muchísimo el nivel de abstracción e independencia de nuestra aplicación.

 

ACTUALIZACIÓN: 12/11/2013

Podemos realizar esta última operación mediante LINQ de una forma aún más sencilla.


        private static Dictionary<string, object> ObtenerElementosLinq(object objeto, MemberTypes TipoElemento)
        {
            try
            {
                // Declaramos un Diccionario que contendra el nombre de los elementos del objeto y el
                //contenido de cada elemento.
                Dictionary<string, object> Elementos = new Dictionary<string, object>();

                // Usamos LINQ para realizar la consulta
                var consulta = from i in objeto.GetType().GetMembers()
                                where ((i.MemberType == TipoElemento) && (i != null))
                                select new KeyValuePair<string,object>(((PropertyInfo)i).Name,
                                                        ((PropertyInfo)i).GetValue(objeto, null));

                // Recorremos la lista de pares clave-valor forzando su ejecución
                foreach (KeyValuePair<string, object> kvp in consulta)
                    Elementos.Add(kvp.Key, kvp.Value);

                // Devolemos el diccionario
                return Elementos;
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }