Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Posts Tagged ‘debugging’

Simplificar la depuración con el uso de DebuggerTypeProxyAttribute

Posted by Gerardo Contijoch en marzo 25, 2009

En el último post les hable sobre DebuggerDisplayAttribute, un atributo que nos permite cambiar la información que nos muestra el debugger de Visual Studio sobre un tipo. Ahora les voy a presentar otro atributo muy similar, pero cuya función es cambiar completamente lo que se muestra de un tipo, incluso sus propiedades. Estoy hablando de DebuggerTypeProxyAttribute. Al aplicar este atributo sobre una clase o estructura lo que hacemos es decirle al debugger que no use nuestra clase (o estructura) cuando inspeccionemos un objeto de ese tipo en el Debugger, sino que use un proxy a la misma. La idea detrás de esto es ocultar los miembros de nuestra clase (porque no son relevantes posiblemente) y mostrar los del proxy, los cuales son los que nos van a interesar ver cuando estemos depurando nuestro código.

Veamos como se vería un posible proxy para una clase Persona (tomada del post anterior y modificada ligeramente) junto a ella:

   1: [DebuggerDisplay("Nombre: {Nombre, nq}, Año de nacimiento: {FechaDeNacimiento.Year}, ¿Es Rosarino?: {Ciudad == \"Rosario\" ? \"Sí\" : \"No\", nq}")]
   2: public class Persona {
   3:     public string Nombre { get; set; }
   4:     public string Apellido { get; set; }
   5:     public int Edad { get; set; }
   6:     public string Ciudad { get; set; }
   7:     public DateTime FechaDeNacimiento { get; set; }
   8:
   9:     public bool EstaCasado { get; set; }
  10:     public Persona Conyuge { get; set; }
  11:
  12:     public Persona Padre { get; set; }
  13:     public Persona Madre { get; set; }
  14:
  15:     public IEnumerable<Persona> Hermanos { get; }
  16:
  17:     public bool PracticaDeportes { get; set; }
  18:     public IEnumerable<string> Deportes { get; }
  19:
  20:     public IEnumerable<string> Estudios { get; }
  21:
  22:     /// <summary>
  23:     /// Proxy para la clase Persona.
  24:     /// </summary>
  25:     internal class PersonaDebugView {
  26:         private Persona Persona { get; set; }
  27:
  28:         public PersonaDebugView(Persona p) {
  29:             Persona = p;
  30:         }
  31:
  32:         public string Nombre {
  33:             get {
  34:                 if (Persona.Madre != null) {
  35:                     return string.Format("{0} {1} {2}", Persona.Nombre, Persona.Apellido, Persona.Madre.Apellido);
  36:                 }
  37:
  38:                 return string.Format("{0} {1}", Persona.Nombre, Persona.Apellido);
  39:             }
  40:         }
  41:
  42:         public string NombreConyuge {
  43:             get {
  44:                 if (Persona.EstaCasado) {
  45:                     return Persona.Conyuge.Nombre;
  46:                 }
  47:
  48:                 return "No tiene conyuge";
  49:             }
  50:         }
  51:
  52:         public bool EsMayorDeEdad {
  53:             get { return Persona.Edad >= 21; }
  54:         }
  55:
  56:         public string PaisDeOrigen {
  57:             get { return RecuperarPaisAPartirDeCiudad(Persona.Ciudad); }
  58:         }
  59:     }
  60: }

Quisiera aclarar un par de puntos en este ejemplo. Primero, vemos que la clase proxy se encuentra dentro de la clase Persona. Esto es recomendable ya de ese modo el proxy puede tener acceso a los miembros privados de la clase que lo contiene y tiene el beneficio extra de que no se mezcla con el resto de las clases de nuestra aplicación. También es un requisito que el constructor del proxy tenga un parámetro del tipo de la clase que se desea ocultar.

Otro detalle que puede haber saltado para los que no leyeron el post anterior (¿qué están esperando?), es el uso de DebuggerDisplayAttribute. Si les interesa saber porque lo uso, pueden encontrar acá el motivo.

Si depuramos nuestra aplicación (aún sin haber aplicado el atributo DebuggerTypeProxyAttribute), veremos que la clase Persona se ve así en el debugger:

DebuggerProxyType-persona

Son muchos datos para una persona y si la mayoría de ellos no nos interesa (recordemos que estamos trabajando sobre un ejemplo) puede volverse un poco incómodo inspeccionar nuestras variables. Veamos ahora como se vería esta pantalla si aplicamos el atributo DebuggerTypeProxyAttribute de la siguiente manera:

   1: [DebuggerTypeProxy(typeof(PersonaDebugView))]
   2: [DebuggerDisplay(/*...*/)]
   3: public class Persona {
   4:     /*...*/
   5: }

DebuggerProxyType-personaconProxy

Las propiedades que estamos viendo son las del proxy (que no tienen que corresponderse necesariamente con las de la clase Persona) y nos muestran una vista totalmente diferente de la variable que estamos inspeccionando.

Presten atención a un nuevo miembro: RawView. Éste último, generado automáticamente, lo que hace es mostrarnos la vista original o sin modificar de nuestra clase y si lo abrimos veremos que tenemos acceso a todas las propiedades de la clase Persona, tal cual la definimos.

El uso de este atributo es muy común en las colecciones en donde se ocultan muchos miembros y solo se muestran unos pocos que son los más relevantes a la hora de trabajar con una colección. Siempre que inspeccionen una variable y vean el miembro RawView, van a estar viendo un proxy para esa clase y no su verdadera implementación.

Aplicando el atributo a nivel de ensamblado

Al igual que con DebuggerDisplayAttribute, DebuggerTypeProxyAttribute puede usarse a nivel de ensamblado exactamente de la misma manera. En el post anterior explico en detalle como se hace esto.

Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, Visual Studio | Etiquetado: , , | 2 Comments »

DebuggerDisplayAttribute, una gran ayuda durante la depuración

Posted by Gerardo Contijoch en marzo 19, 2009

Hace un tiempo, revisando el código fuente de Castle.Core, uno de los componentes más importantes de Castle Microkernel, redescubrí (porque me había olvidado por completo de el) uno de los atributos más útiles que existen en el .NET Framework, DebuggerDisplayAttribute, el cuál nos permite personalizar la información que se muestra de un objeto o miembro en las ventanas de debug en Visual Studio así como también en los tooltips que aparecen al pasar el cursor sobre una variable.

Imaginemos que tenemos la clase Persona definida de la siguiente manera:

   1: public class Persona {
   2:     public string Nombre { get {/*...*/} set {/*...*/} }
   3:     public int Edad { get{/*...*/} set{/*...*/} }
   4:     public string Ciudad { get {/*...*/} set {/*...*/} }
   5:     public DateTime FechaDeNacimiento { get {/*...*/} set {/*...*/} }
   6: }

Normalmente mientras estamos depurando nuestra aplicación al inspeccionar una variable de tipo Persona la ventana QuickWatch (Inspección rápida) nos muestra la variable de la siguiente manera:

DebuggerDisplayAttribute-personaSinNombre

Se puede ver que el valor (columna Value) mostrado es el propio tipo de la variable, lo cual es redundante ya que el tipo aparece aclarado en su propia columna. En nuestro caso, eso no importa mucho ya que al expandir la variable vamos a ver todas sus propiedades, que es lo que realmente nos interesa, pero imaginen que tienen una lista de personas y precisan ubicar una en particular por alguna de sus propiedades. El trabajo de encontrarla puede ser muy tedioso:

DebuggerDisplayAttribute-personasSinNombre

Por eso para ayudarnos en estos casos esta DebuggerDisplayAttribute, el cual nos permite personalizar el valor mostrado en la columna Value de modo que podamos mostrar información más útil que el tipo de la variable o miembro bajo inspección. La propiedad más importante de este atributo es Value (de tipo string) que no es ni más ni menos que el valor a mostrar en la columna Value. Esta cadena puede contener llaves ({}) entre las cuales se puede especificar el nombre de un miembro cuyo valor queremos mostrar. Veamos como podemos decorar nuestra clase con este atributo:

   1: [DebuggerDisplay("Esta persona es {Nombre}")]
   2: public class Persona {
   3:    /*...*/
   4: }

Como resultado de este cambio, ahora la clase se va a ver así en la ventana de Debug:

DebuggerDisplayAttribute-personasConNombre

Si queremos sacar las comillas de los valores de tipo string, simplemente agregamos nq al miembro al que hacemos referencia:

   1: [DebuggerDisplay("Esta persona es {Nombre, nq}")]
   2: public class Persona {
   3:     /*...*/
   4: }

DebuggerDisplayAttribute-personaConNombreSinComillas

Incluso podemos incluir expresiones simples en nuestra descripción, pero sólo si estamos trabajando con C#:

   1: [DebuggerDisplay("Nombre: {Nombre, nq}, Año de nacimiento: {FechaDeNacimiento.Year}, ¿Es Rosarino?: {Ciudad == \"Rosario\" ? \"Sí\" : \"No\", nq}")]
   2: public class Persona {
   3:     /*...*/
   4: }

DebuggerDisplayAttribute-personaConNombreYExpresion

Otra forma de uso

El hecho de usar este atributo para decorar las clases puede no ser muy bien visto ya que es algo que afecta al entorno de desarrollo y no tiene relación con el contenido de nuestras clases en si. Es ajeno a nuestros sistemas y su presencia no afecta (o al menos no debería afectar) al comportamiento de los mismos. Estamos modificando nuestras clases solo porque queremos que Visual Studio nos las muestre como a nosotros más nos gusta.

Para evitar involucrar la lógica de nuestras clases con la lógica de presentación particular de Visual Studio (otro IDE puede no soportar este atributo e ignorarlo por completo) podemos usar este atributo a nivel de ensamblado y crear un nuevo ensamblado con únicamente la declaración del atributo. De este modo, tendremos que especificar, además de la información a mostrar, la clase o estructura sobre la cual queremos aplicarla mediante la propiedad Target.

Para que quede claro esto último lo voy a explicar. La idea de usarlo a nivel de ensamblado es crear una nueva dll (proyecto de tipo Librería de clases). Este proyecto tiene que referenciar al ensamblado que contiene el tipo que queremos decorar. Esto significa que si queremos decorar el tipo XmlDocument, entonces vamos a tener que referenciar al ensamblado System.Xml.dll, en cambio, si queremos decorar nuestra clase Persona, vamos a tener que referenciar a la dll que genere nuestro proyecto.

Una vez dentro del nuevo proyecto buscamos el archivo AssemblyInfo (o cualquier otro archivo, simplemente me parece que éste es el lugar más apropiado para hacerlo) y agregamos algo similar a esto (yo voy a usar mi ejemplo):

   1: [assembly: DebuggerDisplayAttribute("Nombre: {Nombre, nq}, Año de nacimiento: {FechaDeNacimiento.Year}, ¿Es Rosarino?: {Ciudad == \"Rosario\" ? \"Sí\" : \"No\", nq}",
   2:                                     Target = typeof(DebuggerDisplayAttributeTest.Persona))]

Como se ve, usé la propiedad Target para especificar al tipo al que vamos a decorar.

Ahora solo resta compilar este ensamblado y copiarlo al path de visualizadores de Visual Studio (solo el ensamblado, no sus dependencias). Este path varia dependiendo la versión de Visual Studio y Windows. En el caso de Windows Vista, el path tiene la siguiente forma:

%userprofile%\Documents\Visual Studio [VERSION_VS]\Visualizers

Sobre Windows XP y 2003 (e imagino que en 2000 también) es el siguiente:

%userprofile%\My Documents\Visual Studio [VERSION_VS]\Visualizers

Una vez copiado el ensamblado reiniciamos Visual Studio y listo, ya tenemos nuestras clases decoradas sin necesidad de modificar su código.

Si prestan atención, en la carpeta de visualizadores van a encontrar un archivo llamado autoexp.cs. Si inspeccionan el archivo, van a ver que clases de .NET Framework fueron decoradas con este atributo. Son libres de modificarlo (¡no sin antes hacer un backup!) y agregarle todo lo que quieran, pero es responsabilidad de ustedes compilarlo en algún proyecto.

Nos vemos en el próximo post!

Referencias:

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, Visual Studio | Etiquetado: , , | 1 Comment »