martes, 15 de julio de 2014

El Demo del Día: Visor de Objetos con Reflection (NET Object Viewer)

Visor de Objetos con Reflection (NET Object Viewer)

Requerimientos
- Se desea ver los tipos que hay dentro de un ensamblado .NET: interfaces, enumeraciones, clases, etc.
- Se desea listar las propiedades de un tipo.
- Se desea listar los métodos de un tipo.
- Se desea ver los parámetros de un método.
- Se desea abrir dinámicamente un formulario pasando como parámetro su nombre.
- Se desea ejecutar cualquier método pasando los valores de sus parámetros, incluyendo métodos sobrecargados (varios con el mismo nombre pero con diferentes parámetros).

Solución
Usar Reflection para obtener información del Ensamblado, sus Tipos y miembros, también para crear instancias de objetos en forma dinámica y ejecutar cualquier método.

A continuación les detallo el código de un Utilitario que he creado hace varios años pero que lo he mejorado para probar los métodos sobre cargados y para pasar parámetros.

Crear la Aplicación Windows Forms en C#

Crear un proyecto Windows Forms en C# llamado: "NetObjectViewer" y agregar una clase llamada "Rutinas" para poder probar sus métodos mediante el visor.

using System;
using System.Windows.Forms;

namespace NetObjectViewer
{
    public class Rutinas
    {
        public void mostrarMensaje()
        {
            MessageBox.Show("Hola que tal");
        }

        public void mostrarMensaje(string nombre)
        {
            MessageBox.Show("Hola que tal " + nombre);
        }

        public void mostrarMensaje(string nombre,string apellido)
        {
            MessageBox.Show("Hola que tal " + nombre + " " + apellido);
        }

        public int sumaEnteros(int x, int y)
        {
            return (x+y);
        }

        public int sumaEnteros(int x, int y,int z)
        {
            return (x + y +z);
        }

        public decimal sumaDecimal(decimal x, decimal y)
        {
            return (x + y);
        }

        public decimal sumaDecimal(decimal x, decimal y, decimal z)
        {
            return (x + y + z);
        }
    }
}

Crear el Formulario con el Visor de Objetos

Cambiar el nombre del formulario a "frmVisor" y realizar el diseño tal como se muestra a continuación.


Escribir el siguiente código en el formulario:
using System;
using System.Text; //StringBuilder
using System.Drawing;
using System.Collections.Generic; //List
using System.Reflection; //Assembly, Type, PropertyInfo, MethodInfo
using System.Windows.Forms;

namespace NetObjectViewer
{
    public partial class frmVisor : Form
    {
        private Assembly oEnsamblado;
        private Type[] oTipos;

        public frmVisor()
        {
            InitializeComponent();
        }

        private void configurarGrilla(object sender, EventArgs e)
        {
            DataGridViewTextBoxColumn col1=new DataGridViewTextBoxColumn();
            col1.Width=100;
            col1.ReadOnly = true;
            dgvParametro.Columns.Add(col1);
            DataGridViewTextBoxColumn col2 = new DataGridViewTextBoxColumn();
            col2.Width = 80;
            dgvParametro.Columns.Add(col2);
        }

        private void abrirEnsamblado_ListarTipos(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "Selecciona Archivo exe o dll de .NET";
            ofd.Filter = "Archivos ensamblados .NET|*.exe;*.dll";
            if (ofd.ShowDialog().Equals(DialogResult.OK))
            {
                txtEnsamblado.Text = ofd.FileName;
                try
                {
                    oEnsamblado = Assembly.LoadFrom(ofd.FileName);
                    //Limpiar las listas
                    dgvParametro.Rows.Clear();
                    lstMetodo.Items.Clear();
                    lstPropiedad.Items.Clear();
                    lstTipo.Items.Clear();
                    //Listar los tipos
                    oTipos = oEnsamblado.GetTypes();
                    lstTipo.BeginUpdate();
                    foreach (Type oTipo in oTipos)
                    {
                        lstTipo.Items.Add(oTipo.Name);
                    }
                    lstTipo.EndUpdate();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        private void listarMiembros(object sender, EventArgs e)
        {
            Type oTipo = oTipos[lstTipo.SelectedIndex];
            //Limpiar las listas
            dgvParametro.Rows.Clear();
            lstMetodo.Items.Clear();
            lstPropiedad.Items.Clear();
            //Listar las propiedades
            PropertyInfo[] oPropiedades = oTipo.GetProperties();
            lstPropiedad.BeginUpdate();
            foreach (PropertyInfo oPropiedad in oPropiedades)
            {
                lstPropiedad.Items.Add(oPropiedad.Name);
            }
            lstPropiedad.EndUpdate();
            //Listar los metodos
            MethodInfo[] oMetodos = oTipo.GetMethods();
            lstMetodo.BeginUpdate();
            StringBuilder sb=new StringBuilder();
            ParameterInfo[] oParametros;
            foreach (MethodInfo oMetodo in oMetodos)
            {
                sb.Clear();
                sb.Append(oMetodo.Name);
                sb.Append("(");
                oParametros = oMetodo.GetParameters();
                for (int i = 0; i < oParametros.Length; i++)
                {
                    sb.Append(oParametros[i].ParameterType.Name);
                    if (i < oParametros.Length-1) sb.Append(",");
                }
                sb.Append(")");
                lstMetodo.Items.Add(sb.ToString());
            }
            lstMetodo.EndUpdate();
        }

        private MethodInfo obtenerMetodo()
        {
            MethodInfo oMetodo = null;          
            string firmaMetodo = lstMetodo.Text;
            int posInicio = firmaMetodo.IndexOf("(");
            int posFin = firmaMetodo.Length - 1;
            string nombreMetodo = firmaMetodo.Substring(0, posInicio);
            string tipos = firmaMetodo.Substring(posInicio + 1, posFin - posInicio - 1);
            Type[] oPars = null;
            if (tipos.Length.Equals(0)) oPars = new Type[0] { };
            else
            {
                string[] entradas = tipos.Split(',');
                List<Type> listaPars = new List<Type>();
                for (int i = 0; i < entradas.Length; i++)
                {
                    listaPars.Add(Type.GetType("System." + entradas[i]));
                }
                oPars = listaPars.ToArray();
            }
            Type oTipo = oTipos[lstTipo.SelectedIndex];
            oMetodo= oTipo.GetMethod(nombreMetodo, oPars);
            return (oMetodo);
        }

        private void listarParametros(object sender, EventArgs e)
        {
            if (lstMetodo.SelectedIndex > -1)
            {
                MethodInfo oMetodo = obtenerMetodo();
                if (oMetodo != null)
                {
                    ParameterInfo[] oParametros = oMetodo.GetParameters();
                    dgvParametro.Rows.Clear();
                    dgvParametro.RowCount = oParametros.Length;
                    for (int i = 0; i < oParametros.Length; i++)
                    {
                        dgvParametro.Rows[i].Cells[0].Value = oParametros[i].Name;
                    }
                }
            }
            else MessageBox.Show("Selecciona el método a ver sus parámetros");
        }

        private void abrirFormulario(object sender, EventArgs e)
        {
            if (lstTipo.SelectedIndex > -1)
            {
                Type oTipo = oTipos[lstTipo.SelectedIndex];
                if (oTipo.BaseType.Name.Equals("Form"))
                {
                    object obj = Activator.CreateInstance(oTipo);
                    if (obj != null)
                    {
                        Form frm = (Form)obj;
                        frm.ShowDialog();
                    }
                }
                else MessageBox.Show("El tipo seleccionado No es un formulario");
            }
            else MessageBox.Show("Selecciona el formulario a abrir");
        }

        private void ejecutarMetodo(object sender, EventArgs e)
        {
            if (lstMetodo.SelectedIndex > -1)
            {
                MethodInfo oMetodo = obtenerMetodo();
                if (oMetodo != null)
                {
                    object[] pars = null;
                    ParameterInfo[] oParametros = oMetodo.GetParameters();
                    if (oParametros.Length > 0)
                    {
                        List<object> listaPars = new List<object>();
                        Type tipo=null;
                        object valor=null;
                        for (int i = 0; i < oParametros.Length; i++)
                        {
                            tipo = oParametros[i].ParameterType;
                            valor=dgvParametro.Rows[i].Cells[1].Value;
                            if (valor != null) listaPars.Add(Convert.ChangeType(valor, tipo));
                            else listaPars.Add(valor);
                        }
                        pars = listaPars.ToArray();
                    }
                    Type oTipo = oTipos[lstTipo.SelectedIndex];
                    object obj = Activator.CreateInstance(oTipo);
                    if (obj != null)
                    {
                        object rpta = oMetodo.Invoke(obj, pars);
                        if (rpta != null) MessageBox.Show(rpta.ToString());
                    }
                }
            }
            else MessageBox.Show("Selecciona el método a ejecutar");
        }
    }
}

Nota: El método controlador: "configurarGrilla"esta asociado al evento "Load" del formulario.

Ejecutar y Probar el Formulario Creado

Grabar la aplicación, compilar y ejecutar. En el evento load se crean las 2 columnas de la grilla de parámetros del método seleccionado.

Seleccionar un ensamblado, por ejemplo el propio ejecutable: "NetObjectViewer.exe" y luego seleccionar un tipo, por ejemplo el propio formulario.


Estando seleccionado el formulario "frmVisor" clic al botón "Abrir Formulario" y este se mostrará como un diálogo.

Seleccionar la clase "Rutinas" y luego seleccionar cualquier método, observar que aparece la grilla mostrando sus parámetros. Probar primero las funciones que no retornan valores y luego las que retornan valores, dando clic al botón "Ejecutar Método".

Si no se llenan los valores de los parámetros, por defecto es nulo, pero igual devuelve un resultado, ahora probar pasar parámetros escribiendo directamente sobre la segunda columna de la grilla y dando Enter para configurar el valor del parámetro. Luego clic al botón "Ejecutar Método" y ver el resultado con parámetros.


Nota: Si se selecciona un método que tiene parámetros de tipo objeto, la aplicación se caerá, falta controlar los parámetros de tipo objetos.

Comentario Final

En esta demostración hemos visto "Reflection" en acción para inspeccionar cualquier ensamblado (exe o dll) creado en .NET, listando algunos miembros como propiedades y métodos, también hemos creado dinámicamente objetos y ejecutado métodos, inclusive sobrecargados. Espero les guste y comenten.

Descarga
Demo11_NetObjectViewer

3 comentarios:

  1. Buen aporte profe, ahora entiendo que la magia esta en usar reflection.

    ResponderBorrar
  2. Hay que tener cuidado en usar Reflection, solo para creación de Controles, Librerías Genéricas o Utilitarios. Si se aplica intensivamente para reducir código en las aplicaciones de datos puede ser mas costoso ya que el uso intensivo de Reflection puede aumentar tiempo y recursos en el procesamiento.

    ResponderBorrar