Uso extendido de DebuggerDisplay en C#
En el namespace System.Reflection podemos encontrar verdaderas joyas que nos proporcionan información muy útil.
Uno de los atributos que podemos encontrar ahí dentro es el llamado como DebuggerDisplay.
Como todos sabemos, si algo hace seguro sí o sí un programador, es probar su código y pasar la mayor parte de su tiempo depurando.
Aunque existen herramientas de terceros que nos facilita la vida cuando depuramos nuestro código, no es menos cierto que este atributo nos ayuda mucho permitiéndonos mostrar información concreta de nuestra clase en la ventana de depuración.
Pero veamos un ejemplo de una clase muy simple Person que contiene dos propiedades, una Name y otra Age.
public class Person { public string Name { get; set; } public int Age { get; set; } }
Si desde nuestra aplicación de consola tenemos algo parecido a esto:
var person = new Person() { Name = "Mary", Age = 21 };
Cuando depuramos nuestro código veremos lo que contiene person.
Algo parecido a esto:
El problema que podemos encontrar aquí es que tenemos que entrar en person para ver el valor de las propiedades que contiene la clase.
Pero, ¿cómo deberíamos actuar si queremos ahorrarnos algo de tiempo y ser más productivos?.
Precisamente el atributo DebuggerDisplay nos va a ayudar enormemente.
Con el atributo DebuggerDisplay podemos indicar en la cabecera de la clase qué queremos que se muestre en la ventana de depuración.
Podríamos hacer algo así:
[DebuggerDisplay("Name Age")] public class Person { public string Name { get; set; } public int Age { get; set; } }
En tiempo de depuración, obtendremos entonces algo como esto:
Ahora bien, podemos rizar el rizo y pensar que esto requeriría que cada vez que cambiamos la clase, tengamos que acordarnos de cambiar el atributo para acomodarlo a los cambios.
No debemos olvidar nada y debemos ser algo estrictos.
¿Cómo podríamos cambiar este comportamiento?, ¿cómo podríamos hacerlo genérico?.
Me gustaría compartir aquí una forma de hacer esto más dinámico y genérico a aquellas clases que nos interesa decorar con este atributo y sobre las que queremos realizar el mantenimiento mínimo.
Para ello, voy a crear una clase genérica que se encargará de ver las propiedades que hay dentro de la clase y mostrar la información que para nosotros sea relevante dentro de la ventana de depuración.
La clase genérica sería la siguiente:
public class DebuggerDisplayFrom<T> { public static string PropertiesOf(T o) { var data = String.Empty; foreach (PropertyInfo propertyInfo in o.GetType().GetProperties()) { data += "{" + $"{propertyInfo.ToString()}:{propertyInfo.GetValue(o, null)}" + "}"; } return data; } }
Esta clase genérica se encargará de obtener todas las propiedades de una clase y mostrarlas junto a sus valores.
Algo similar a esto:
Sin embargo, podríamos encontrarnos con la necesidad de que sólo querríamos mostrar algunas de las propiedades de la clase y no todas, por lo que aquí, la forma que se me ocurre de resolver este problema sería crear un atributo que nos facilitara la posibilidad de indicar si una determinada propiedad queremos hacerla visible en depuración a través del atributo DebuggerDisplay.
El resultado es la creación de un atributo de esta forma:
public class DebuggerDisplayMemberAttribute : Attribute { private bool _debuggerDisplay; public bool DebuggerDisplay { get { return _debuggerDisplay; } } public DebuggerDisplayMemberAttribute(bool debuggerDisplay = true) { _debuggerDisplay = debuggerDisplay; } }
Nuestro atributo tendrá un valor por defecto true.
Decorar una propiedad con este atributo sin indicar su valor, hará que sea visible en la clase genérica que indicaré a continuación.
Omitir el uso de este atributo en una propiedad tendrá el mismo efecto que decorarla como false.
La clase genérica por lo tanto, requerirá igualmente de una adaptación para que nos permita chequear la existencia de este atributo y actuar en consecuencia.
Nuestra clase genérica quedará por lo tanto de esta forma:
public class DebuggerDisplayFrom<T> { private const string OUTPUT_PATTERN = "{{{0}:{1}}}"; public static string PropertiesOf(T o) { var data = String.Empty; foreach (PropertyInfo propertyInfo in o.GetType().GetProperties()) { foreach (Attribute attribute in propertyInfo.GetCustomAttributes(false)) { var debuggerDisplayMemberAttribute = (DebuggerDisplayMemberAttribute)attribute; if (debuggerDisplayMemberAttribute.DebuggerDisplay) data += String.Format(OUTPUT_PATTERN, propertyInfo.ToString(), propertyInfo.GetValue(o, null)); } } return data; } }
Nuestra clase Person quedará por lo tanto definida de la siguiente forma:
[DebuggerDisplay("{DebuggerDisplayFrom<Person>.PropertiesOf(this)}")] public class Person { [DebuggerDisplayMember] public string Name { get; set; } public int Age { get; set; } }
Y la salida que obtendremos en depuración será la siguiente:
Como vemos, el atributo DebuggerDisplay nos puede dar cierto juego a la hora de depurar nuestros desarrollos aportándonos cierta información que puede ser relevante e incluso nos puede hacer ser más productivos.
Aunque había pensado en un principio hacer un paquete NuGet de esta solución, tiene tantas posibles alternativas, personalizaciones y ajustes, que prefiero no hacer un paquete NuGet de esto pero sí compartir un proyecto en GitHub para que veas cómo funciona y cómo podemos usarlo.
El ejemplo funciona tanto en .NET Framework como en .NET Core.
De hecho, encontrarás todos los proyectos en la misma solución de Visual Studio 2017 a la que podrás acceder en este enlace.
Happy Coding!
2 Responsesso far
Muy buena tu aportaciòn, te agradezco por compartir tu conocimiento de una forma muy didactica
Muchas gracias por tus palabras Renan. 🙂