Torre de Babel

Cómo recuperar información de tipos en ejecución en la plataforma .NET

by Francisco Charte.

Introducción 

La meta información, o información tipos, es generada automáticamente para todos los tipos de datos en el momento de la compilación. Parte de esa información es intrínseca al propio tipo de dato: enumeración, estructura, clase, etc., siendo posible añadir otra mediante el uso de atributos. Éstos se emplean para indicar características concretas de un componente o un servicio Web, delimitándose entre corchetes o entre los caracteres < y > según el lenguaje en que trabajemos.

Nuestro objetivo, en este artículo, es aprender a recuperar esa meta información durante la ejecución de un programa. La mayoría de los programas no necesitan dicha información, pero puede ser muy útil en ciertos casos, especialmente cuando una aplicación va a operar sobre elementos cuyas características desconoce de antemano. El diseñador de formularios Windows, por poner un ejemplo que seguramente conoce suficientemente, no tiene posibilidad alguna de conocer las propiedades y eventos de todos los componentes que pudieran desarrollarse y, en la práctica, no lo necesita, ya que puede obtener esa información cuando la necesita. Eso es lo que nos interesa saber a nosotros, cómo obtenerla cuando sea precisa.

El código de ejemplo de este artículo está escrito en lenguaje C#, pero el uso de los servicios es extensible a cualquiera de los lenguajes .NET, como Visual Basic .NET, JScript .NET, Visual C++ .NET o J# .NET.

Servicios de reflexión

A la hora de obtener información sobre tipos en ejecución nos tendremos que servir de los servicios de reflexión de la plataforma .NET, cuyas clases encontraremos en el ámbito System.Reflection. En este ámbito encontramos clases que podríamos denominar de primer nivel, como Module o Assembly, y otras cuya finalidad es ofrecer información sobre un determinado elemento, como MemberInfo, ParameterInfo o EventInfo. Otra clase fundamental en este contexto, aunque se encuentra en el ámbito System al ser básica para la plataforma, es Type.

Ya sabemos que todos los objetos con los que tratamos en Visual C# .NET están, directa o indirectamente, derivados de System.Object, incluso los tipos de datos que almacenan un valor y no una referencia. La clase Object dispone, entre otros, de un método llamado GetType() que devuelve un objeto Type con información sobre el objeto en cuestión. Así, es fácil recuperar información a partir de una variable o una propiedad, por ejemplo.

Vamos a comenzar, no obstante, en un nivel bastante más alto que el de las variables o propiedades que pueda contener un objeto, concretamente al nivel de los ensamblados y los módulos que los componen. Después iremos descendiendo rama a rama hasta llegar al detalle máximo como puede ser la lista de parámetros de un método.

Aunque en este artículo vamos a ocuparnos principalmente de los servicios que nos permiten recuperar información sobre los tipos, utilizando ciertos servicios del ámbito System.Reflection podemos también crear esa información. Son los servicios utilizados por los compiladores y otras herramientas.

Ensamblados y módulos

Cada vez que compilamos un proyecto en Visual C# .NET estamos generando un ensamblado, generalmente compuesto de un solo módulo que puede ser un ejecutable o una biblioteca. Las propiedades de ese ensamblado, y las acciones que pueden efectuarse sobre él, están representadas por un objeto de la clase Assembly, alojada en el ámbito System.Reflection. Normalmente obtendremos un objeto de esa clase mediante dos mecanismos distintos: el método estático GetExecutingAssembly(), que nos entrega el objeto que representa al ensamblado en el que se encuentra el código que está ejecutándose, o bien los métodos Load() y LoadFrom(), también estáticos, que nos permiten abrir cualquier ensamblado obteniendo un objeto Assembly con su información.

Disponiendo de un objeto Assembly, recuperado con cualquiera de los métodos anteriores, podemos obtener información diversa acerca de él leyendo ciertas propiedades. También podemos enumerar todos los módulos que lo componen, como veremos de inmediato.

Información sobre el ensamblado

Un ensamblado tiene un nombre, una localización física, un punto de entrada, etc. Todos éstos son datos que podemos recuperar sirviéndonos de las propiedades de la clase Assembly. Para verlo en la práctica nos serviremos de una sencilla aplicación de consola en la que incluiremos el código siguiente:

// Para poder utilizar los servicios de reflexión

using System.Reflection;
using System;&nbsp;<o:p>
</o:p>
                          
class Aplicación
{
  static void Main()
  {
    // Obtenemos el objeto Assembly que representa

    // al ensamblado que contiene este mismo código

    Assembly EsteEnsamblado = 
      Assembly.GetExecutingAssembly()    // Mostramos por la consola el nombre, localización 

    // y otras propiedades

    Console.WriteLine("Nombre del ensamblado: {0}", 
      EsteEnsamblado.FullName);
    Console.WriteLine();
    Console.WriteLine("Localización física: {0}", 
      EsteEnsamblado.Location);
	Console.WriteLine();
    Console.WriteLine("CodeBase: {0}", 
      EsteEnsamblado.CodeBase);
    Console.WriteLine();
	Console.WriteLine("Está en la caché de ensamblados: {0}", 
      EsteEnsamblado.GlobalAssemblyCache);

    Console.WriteLine();
    Console.WriteLine("EntryPoint: {0}", 
      EsteEnsamblado.EntryPoint);
  }
}

Tras obtener una referencia al objeto Assembly que representa al ensamblado actual, el que contiene el código que está ejecutándose, usamos las propiedades FullName, Location, CodeBase, GlobalAssemblyCache y EntryPoint para recuperar el nombre del ensamblado, su localización física, saber si está o no en la caché global de ensamblados del sistema y conocer el punto de entrada que se ejecuta al iniciar el ensamblado. El resultado obtenido, al ejecutar este programa, será similar al de la figura 1. En ella puede ver que el nombre completo incluye la versión del ensamblado, así como que éste no se encuentra en la GAC (Global Assembly Cache) y que el punto de entrada es el método Main().


Figura 1. Información sobre el ensamblado que contiene el propio ejemplo.

Si en lugar de sobre el ensamblado que contiene el ejemplo desea información sobre otro cualquiera, puede usar el método Load() para recuperarlo. Éste necesita el nombre del ensamblado, en caso de ser privado a nuestra aplicación, o bien una referencia completa incluyendo versión, cultura y la clave pública, como se ve en la sentencia siguiente:

                          
EsteEnsamblado = Assembly.Load(
  "System.Windows.Forms,version=1.0.3300.0, culture=neutral," +
  "PublicKeyToken=b77a5c561934e089");

En este caso se recupera el ensamblado System.Windows.Forms, pero de manera análoga podría acceder a cualquier otro ya sea de la plataforma .NET o de terceros. El resultado, como se aprecia en la figura 2, es bien distinto. Este ensamblado sí se encuentra alojado en la GAC y no tiene un punto de entrada ya que es una biblioteca, no ejecutable directamente.


Figura 2. Información sobre el ensamblado System.Windows.Forms.

Módulos que componen el ensamblado

La clase Assembly dispone de varios métodos que nos permiten obtener un cierto módulo de los que componen el ensamblado o bien una lista de todos ellos o todos los que en ese momento están alojados en memoria. Cada módulo es representado mediante un objeto de la clase Module.

En la clase módulo tan sólo encontramos cuatro propiedades con información sobre el módulo. Con ellas podemos conocer el nombre simple o cualificado, así como obtener el Assembly al que pertenece el módulo. No necesitamos más para, simplemente, enumerar los módulos que tiene un ensamblado conociendo su nombre. Podemos añadir las tres sentencias siguientes al final del código del ejemplo anterior para conseguir un resultado como el de la figura 3. En este caso el ensamblado está compuesto de un solo módulo.

foreach(Module UnModulo in EsteEnsamblado.GetModules())
      Console.WriteLine(UnModulo.Name);


Figura 3. Mostramos la lista de módulos que componen el ensamblado.

En este caso hemos usado el método GetModules() para enumerar los módulos del ensamblado. También podemos usar el valor devuelto como un arreglo, utilizando un índice numérico para acceder a cada uno de los módulos. El primero, que tiene por índice cero, es siempre el módulo que podríamos considerar principal, donde se encuentra el punto de entrada.

Tipos definidos en un ensamblado

Un ensamblado es una unidad lógica, formada por uno o más módulos físicos, en el que viven los tipos de datos públicos definidos en éstos. Son tipos las clases, estructuras, enumeraciones, delegados, etc. Al igual que podemos saber qué módulos forman un ensamblado, también es fácil conocer la lista de tipos que hay en su interior, así como conocer los detalles de cada tipo.

En este caso el método que nos interesa se llama GetTypes() y podemos encontrarlo tanto en la clase Assembly como en Module. En el primer caso obtendríamos una lista de los tipos existentes en el ensamblado, mientras que en el segundo la lista sería únicamente de los tipos de un cierto módulo. En nuestro caso, tomando como base el ejemplo anterior, el resultado sería el mismo dado que el ensamblado se compone de un solo módulo.

Lo que devuelve el método GetTypes() es un arreglo de objetos de la clase Type. Como se indicó anteriormente, es posible obtener un objeto de esa clase con la información de cualquier variable u objeto mediante el método GetType() definido en la clase Object, ascendiente común de todos los tipos existentes en Visual C# .NET.

La clase Type

A pesar de estar definida en el ámbito System y ser una clase fundamental para el funcionamiento de los diseñadores y editores de Visual C# .NET, la clase Type está derivada no de Object sino de MemberInfo, una clase que sí se aloja en el ámbito System.Reflection.  Mientras que MemberInfo está pensada para facilitar información de los miembros de una clase o estructura, Type tiene un objetivo más general y puede utilizarse tanto para obtener datos de una clase como de cualquiera de sus miembros.

Uno de los pocos miembros que Type hereda de MemberInfo es Name, una propiedad en la que encontraremos el nombre del tipo. Generalmente el nombre no nos dirá mucho a menos que conozcamos el tipo de antemano, caso en el cual no necesitaríamos los servicios de reflexión de la plataforma .NET. Mediante una treintena de métodos de la clase Type, todos ellos siguen la nomenclatura IsXXXX() y devuelven un valor de tipo bool, podremos saber si el elemento es una clase: IsClass(); una interfaz: IsInterface(); una enumeración: IsEnum(); tiene ámbito público: IsPublic(); o no: IsNotPublic(), es de uso final y no puede utilizarse como base: IsSealed(), etc. Tan sólo tiene que consultar la documentación de referencia sobre la clase Type para conocer todos los métodos disponibles.

Una vez determinada la naturaleza del tipo, podemos utilizar los métodos de la clase Type para recuperar una lista de sus constructores, miembros, propiedades, métodos, eventos, etc. También podemos comprobar si el tipo cuenta con un miembro en concreto. Los métodos que nos interesan, en este caso, son GetMethods(), GetEvents(), GetConstructors() y GetProperties(), para el primer supuesto, o FindMembers() en el segundo.

La información devuelta por todos esos métodos son objetos de clases como MethodInfo, EventInfo, ConstructorInfo y PropertyInfo, que se caracterizan por contar con elementos que nos permiten determinar, por ejemplo, el tipo de una propiedad, la lista de parámetros de un método. Todas esas clases están derivadas de MemberInfo, por lo que pueden tratarse de manera genérica determinando el tipo de cada miembro mediante la propiedad MemberType de MemberInfo. Ésta puede tener uno de los valores enumerados en la tabla 1. Con un simple condicional podríamos convertir al tipo adecuado y obtener información específica sobre él.

Tabla 1. Valores de la enumeración MemberTypes

Constante

El miembro es ...

Constructor Un constructor
Event Un evento
Field Una variable
Method Un método
Property Una propiedad
NestedType Clase, interfaz, estructura, etc.
Custom Personalizado

En caso de que el valor de MemberType sea NestedType nos encontramos con la definición de un nuevo tipo. Es lo que ocurre, por ejemplo, cuando una clase alberga la definición de otra clase.

Parámetros de un método

En caso de que tengamos información sobre un método, los métodos de acceso de una propiedad o un constructor, puede interesarnos conocer la lista de parámetros que necesita para ser invocado. La información de un método se devuelve en un objeto de clase MethodInfo, mientras que la de un constructor se entrega en un objeto ConstructorInfo. Ambos están derivados de la clase MethodBase, de tal forma que cuentan con muchos elementos en común.

Si lo que tenemos es una propiedad, podemos obtener el objeto MethodInfo correspondiente a su método de lectura mediante GetGetMethod() y el de escritura con GetSetMethod(). De esta forma, nos basta con conocer uno de los métodos de la clase MethodBase: GetParameters(). Éste devuelve un arreglo de objetos ParameterInfo, conteniendo cada uno de ellos el nombre, tipo y atributos de un parámetro.

Hay que tener en cuenta que todas las clases mencionadas cuentan con el método GetType() y, por tanto, podemos obtener su tipo y descubrir información acerca de ellas de forma dinámica. También es importante conocer las relaciones entre algunas de estas clases para conocer las posibles conversiones. El tipo MemberInfo, por ejemplo, es genérico y puede contener una referencia a un MethodInfo o un PropertyInfo, pudiendo convertirse a dichos tipos mediante el correspondiente operador.

Jerarquía de tipos de un ensamblado

Como resumen de lo visto hasta ahora sobre el descubrimiento de tipos en ejecución, información que deberá complementar con la referencia de Visual Studio .NET sobre las clases mencionadas, vamos a desarrollar un sencillo programa que muestre un árbol jerárquico de los tipos contenidos en un ensamblado, enumerando también sus miembros. Nuestro objetivo es obtener un resultado como el de la figura 4, que corresponde al programa ya en funcionamiento y mostrando el contenido del ensamblado JerarquiaTipos. Como se aprecia en dicha figura, el ensamblado tiene tres tipos de primer nivel: Dias, Agenda y Form1, que son una enumeración, una estructura y una clase, respectivamente. Debajo podemos ver todos sus miembros y, entre paréntesis, la categoría a la que pertenece. En el caso de los métodos también se facilita la lista de parámetros.


Figura 4. Vista jerárquica de tipos de un ensamblado.

El componente que ocupa todo el espacio disponible en la ventana es un TreeView,que se caracteriza por contar con uno o más nodos de primer nivel que pueden contener a otros en su interior. Cada nodo se corresponde con un objeto TreeNode, clase que dispone de una propiedad, llamada Nodes, conteniendo la colección de nodos hijo. Existe un método Add() que se encarga de añadir nuevos nodos y devolver una referencia a ellos, de tal forma que es fácil construir un árbol como el de la figura 4.

Asumiendo que hemos iniciado una nueva aplicación basada en formularios Windows e insertado un control TreeView en su interior, modificando la propiedad Dock para que ocupe todo el espacio disponible, el código que deberíamos introducir sería el siguiente:

						  
namespace JerarquiaTipos
{
using System.Reflection;
// Una enumeración

  public enum Dias
  {
    Lunes,
    Martes,
    Miércoles,
    Jueves,
    Viernes,
    Sabado,
    Domingo,
  }

  // y una estructura para tener algunos tipos

  // en el ensamblado 

  public struct Agenda
  {
    public Agenda(int mIDe, string mNombre, string mTelefono)
    {
      ID = mIDe;
      Nombre = mNombre;
      Telefono = mTelefono;
    }
	public int ID;
    public string Nombre;
    public string Telefono;
  }
  
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.TreeView treeView1;
	// Al abrirse el formulario

    private void Form1_Load(object sender, System.EventArgs e)
    {
      // obtenemos una referencia al ensamblado

      Assembly EsteEnsamblado = Assembly.Load("JerarquiaTipos");
	  // y añadimos su nombre como raíz

      TreeNode Nodo = treeView1.Nodes.Add(EsteEnsamblado.FullName);
	  // Enumeramos todos sus tipos 

      foreach(Type Tipo in EsteEnsamblado.GetTypes())
        EnumeraTipos(Nodo, Tipo);
    }
	
	// Por cada tipo que exista

    private void EnumeraTipos(TreeNode Nodo, Type Tipo)
    {
      // añadimos un nodo hijo al árbol con el nombre 

      // y el tipo base

      TreeNode Hijo = Nodo.Nodes.Add(Tipo.Name +
        " Inherits " + Tipo.BaseType.Name);
		EnumeraMiembros(Hijo, Tipo); // y enumeramos sus miembros

    }
	
	// Por cada miembro de los existentes en una clase

    private void EnumeraMiembros(TreeNode Nodo, Type Tipo)
    {
      // Enumeramos todos los miembros

      foreach(MemberInfo Info in Tipo.GetMembers())
      {
        // añadiendo su nombre y el tipo de miembro

        TreeNode Hijo = Nodo.Nodes.Add(Info.Name + " (" +
          Info.MemberType.ToString() + ")");
		  // si el miembro es un método

          if(Info.MemberType == MemberTypes.Method)
          // enumeramos los parámetros

          EnumeraParametros(Hijo, Info);
      }
    }
	
	// Por cada parámetro existente en un método

    private void EnumeraParametros(TreeNode Nodo,
      MemberInfo InfoMetodo)
    {
      // Enumeramos todos los parámetros

      foreach(ParameterInfo Info in 
        ((MethodInfo )InfoMetodo).GetParameters())
        // añadiendo su nombre y tipo

        Nodo.Nodes.Add(Info.Name + " - " + 
          Info.ParameterType.ToString());
    }
  }
}

El código prácticamente está auto-documentado con los comentarios que se han introducido. Uno de los pocos puntos destacables es la llamada al método EnumeraParametros() desde EnumeraMiembros(). En éste último se dispone de un objeto de clase MemberInfo con información genérica sobre el tipo de miembro, objeto que se entrega al primero. En dicho método se efectúa una conversión de MemberInfo a MethodInfo para poder así recuperar la lista de parámetros. Esto es posible, lógicamente, porque la variable Info del método EnumeraMiembros() aunque es de tipo MemberInfo apunta a un objeto MethodInfo, hecho que hemos comprobado mediante la propiedad MemberType. Si ésta no contuviese el valor MemberTypes.Method la citada conversión provocaría una excepción.

Uso dinámico de objetos

Si la información recuperada durante la ejecución tan sólo nos sirviera para mostrarla al usuario, lo cierto es que su utilidad sería mínima. Una aplicación que usa los servicios de reflexión para recuperar información generalmente la utiliza con otro fin, no para mostrarla al usuario, como puede ser el acceso dinámico a los objetos, sin conocer previamente su localización ni tipo.

Algunas de las clases citadas en los puntos previos, como MethodInfo y PropertyInfo, disponen de miembros que facilitan su invocación, recuperación o modificación de valor. MethodInfo, por ejemplo, cuenta con un método llamado Invoke() que efectúa una llamada al método representado sobre un objeto que debemos facilitar como parámetro. De manera análoga, PropertyInfo cuenta con los métodos GetValue() y SetValue() cuya finalidad es fácil suponer.

En la mayoría de aplicaciones, si queremos utilizar un cierto objeto, por ejemplo un formulario, definimos éste en un módulo del mismo proyecto o, como mucho, accedemos a una clase alojada en una biblioteca de enlace dinámico cuya referencia hemos importado. En definitiva, el entorno conoce el tipo del objeto a crear y usar, de tal forma que permite crearlo y acceder a sus métodos y propiedades sin ningún problema.

Las dificultades surgen cuando los objetos a usar no tienen tipos conocidos durante el desarrollo de la aplicación, por ejemplo porque se vayan a facilitar posteriormente en forma de bibliotecas fácilmente actualizables. ¿Cómo podemos crear un objeto de una clase sin disponer de una referencia a ella durante la fase de desarrollo? En ese caso usaríamos lo que se denomina early binding o enlace temprano o en fase de compilación. Como no existe esa posibilidad, tendremos que recurrir al late binding o enlace tardío o en fase de ejecución.

La clase Activator

El primer paso para poder utilizar una clase mediante enlace en fase de ejecución, creándola y accediendo a sus miembros de manera dinámica, es crear un objeto a partir de esa clase. Ése es el cometido de los métodos estáticos de la clase Activator, que podemos usar tanto para crear objetos locales como remotos, así como para recuperar referencias a objetos que ya están ejecutándose en algún punto.

Los dos métodos que más nos interesan de esta clase son CreateInstance() y CreateInstanceFrom(). El primero de ellos crea un objeto de una clase a partir de su información de tipo, que deberemos facilitar mediante un objeto Type. Ya sabemos que podemos abrir un ensamblado y enumerar todos sus tipos, por lo que no tendríamos más que entregar esos tipos a CreateInstance() para crear objetos. El segundo método, CreateInstanceFrom(), necesita dos parámetros: el nombre de un ensamblado y el nombre de la clase que vamos a usar para crear el objeto. En un solo paso se abre el ensamblado, localiza el tipo y crea el objeto, sin necesidad de pasos previos.

Asumiendo que hemos creado una biblioteca de clases y alojado en ella una clase llamada Formulario en el ámbito Formulario, podríamos usar el código siguiente, en otro proyecto alojado en la misma carpeta, para abrir el ensamblado, recuperar una referencia al tipo y crear el objeto.

                          
// obtenemos una referencia al ensamblado

    Assembly Ensamblado = Assembly.LoadFrom(
      "..\\..\\..\\Formulario\\bin\\debug\\Formulario.dll");

    // y localizamos el tipo que nos interesa

    Type Clase = Ensamblado.GetType("Formulario.Formulario");

    // utilizándolo para crear un objeto

    Object Objeto = Activator.CreateInstance(Clase)

Observe que el tipo de la variable Objeto es Object y no Formulario. No podríamos usar el tipo Formulario como tal ya que en el proyecto no existe ninguna referencia a él, por eso utilizamos una referencia genérica a un objeto, sin conocer nada más de él.

Nota: Para poder probar el código anterior, y el del punto siguiente, cree una biblioteca de clases y defina en ella una clase llamada Formulario derivada de Form y que cuente con un solo método, llamado Saludos(), que se limite a mostrar un mensaje.

Invocación dinámica

Disponiendo de una referencia a nuestro objeto en la variable Objeto, podríamos invocar al método Saludos() o, puesto que Formulario es una clase derivada de Form, modificar cualquiera de las propiedades e invocar a cualquier método heredado. No podemos, sin embargo, escribir sentencias tales como Objeto.Visible = true, ya que Objeto es de tipo Object y en dicho tipo no existe la propiedad Visible.

La variable Clase definida en el anterior fragmento de código, conteniendo la información de tipo de nuestro objeto, puede ser utilizada para obtener datos sobre los métodos y propiedades de la clase, tan sólo tendríamos que usar GetMethod() y GetProperty(), en este caso, para obtener un objeto MethodInfo o PropertyInfo con información sobre el método o propiedad que nos interese.

En el código siguiente se utiliza esta técnica para invocar a los métodos Saludos() y Show(), así como para modificar el valor de la propiedad Text. Al ejecutar el código aparecerá primero un mensaje y, a continuación, el formulario con el título modificado.

// Obtenemos una referencia a un método

  MethodInfo Metodo = Clase.GetMethod("Saludos");
  Metodo.Invoke(Objeto, null); // y lo llamamos


  // Obtenemos una referencia a una propiedad

  PropertyInfo Propiedad = Clase.GetProperty("Text");

  // y modificamos su valor

  Propiedad.SetValue(Objeto, "Nuevo título de la ventana", null);

  // Cambiamos la referencia a otro método

  Metodo = Clase.GetMethod("Show");
  Metodo.Invoke(Objeto, null); // y lo llamamos

En este ejemplo hemos asumido que el ensamblado Formulario.dll contenía un tipo Formulario en el ámbito Formulario y que dicho tipo, una clase, dispone de ciertos métodos y propiedades. En la práctica, sin embargo, podemos usar los métodos explicados anteriormente para conocer los tipos que hay en el ensamblado, identificar los métodos y propiedades, así como sus tipos, e invocarlos realmente sin conocerlos de manera previa. No obstante, sólo con lo que hemos hecho tenemos un programa que muestra un formulario alojado en una biblioteca de enlace dinámico sin contar con un enlace en la fase de diseño. Esto nos permite modificar esa biblioteca siempre que nos interese, por ejemplo modificando el diseño del formulario o la definición del método Saludos(), sin por ello tener que recompilar la aplicación principal. Bastaría con redistribuir la biblioteca para actualizar el programa, siempre que se conserve el nombre del tipo.

Nota: En lugar de facilitar al método LoadFrom() de la clase Assembly una constante, con el camino y nombre de la biblioteca, también existe la posibilidad de permitir seleccionar al usuario la biblioteca o que ésta venga determinada por algún parámetro de configuración.

Resumen

Mediante las técnicas que hemos conocido en este artículo podemos conocer en ejecución objetos y elementos que en fase de diseño no se conocían, obteniendo información sobre clases, enumeraciones, estructuras, métodos, propiedades, eventos y, en general, todos los componentes de un ensamblado. Estos servicios son los que utilizan los propios diseñadores de Visual C# .NET para, por ejemplo, facilitar una lista de miembros cuando en el editor se introduce el nombre de un objeto o enumerar las propiedades en la ventana Propiedades.

Lo más interesante de estos servicios, a menos que vayamos a crear algún diseñador o herramienta similar, es que nos permiten utilizar objetos de forma dinámica, sin conocerlos de antemano, aportando mucha flexibilidad al diseño de una aplicación. Hemos visto cómo es posible usar un elemento alojado en una biblioteca, biblioteca que podría actualizarse siempre que fuese necesario sin afectar a la aplicación principal.