Cómo describir los elementos de una enumeración usando métodos de extensión y atributos (C# y VB.NET)

Me he encontrado en el blog de Fresh Logic Studios con un post donde describen una técnica interesante para obtener descripciones textuales de los elementos de una enumeración. De hecho, ya la había visto hace tiempo en I know the answer como una aplicación de los métodos de extensión para mejorar una solución que aportaba en un post anterior.

Como sabemos, si desde una aplicación queremos obtener una descripción comprensible de un elemento de una enumeración, normalmente no podemos realizar una conversión directa (elemento.ToString()) del mismo, pues obtenemos los nombres de los identificadores usados a nivel de código. La solución habitual, hasta la llegada de C# 3.0 consistía en incluir dentro de alguna clase de utilidad un conversor estático que recibiera como parámetro en elemento de la enumeración y retornara un string, algo así como:

        
public static string EstadoProyecto2String(EstadoProyecto e)
{
switch (e)
{
case EstadoProyecto.PendienteDeAceptacion:
return «Pendiente de aceptación»;
case EstadoProyecto.EnRealizacion:
return «En realización»;
case EstadoProyecto.Finalizado:
return «Finalizado»;
default:
throw
new ArgumentOutOfRangeException(«Error: » + e);
}
}


Este método, sin embargo, presenta algunos inconvenientes. En primer lugar, dado el tipado fuerte del parámetro de entrada del método, es necesario crear una función similar para cada enumeración sobre la que queramos realizar la operación.

También puede resultar peligroso separar la definición de la enumeración del método que transforma sus elementos a cadena de caracteres, puesto que puede perderse la sincronización entre ambos cuando, por ejemplo, se introduzca un nuevo elemento en ella y no se actualice el método con la descripción asociada.

La solución, que como he comentado me pareció muy interesante, consiste en decorar cada elemento de la enumeración con un atributo que describa al mismo, e implementar un método de extensión sobre la clase base System.Enum para obtener estos valores. Veamos cómo.

Ah, una cosa más. Aunque los ejemplos están escritos en C#, se puede conseguir exactamente el mismo resultado en VB.NET simplemente realizando las correspondientes adaptaciones sintácticas. Podrás encontrarlo al final del post.


1. Declaración de la enumeración


Vamos a usar el atributo System.ComponentModel.DescriptionAttribute, aunque podríamos usar cualquier otro que nos interese, o incluso crear nuestro propio atributo personalizado. El código de definición de la enumeración sería así:


using System.ComponentModel;

public enum EstadoProyecto
{
[Description(«Pendiente de aceptación»)] PendienteDeAceptacion,
[Description(«En realización»)] EnRealizacion,
[Description(«Finalizado»)] Finalizado,
[Description(«Facturado y cerrado»)] FacturadoYCerrado
}


 


2. Implementación del método de extensión


Ahora vamos a crear el método de extensión (¿qué son los métodos de extensión?) que se aplicará a todas las enumeraciones.

Fijaos que el parámetro de entrada del método está precedido por la palabra reservada this y el tipo es System.Enum, por lo que será aplicable a cualquier enumeración.


using System;
using System.ComponentModel;
using System.Reflection;

public static class Utils
{
public static string GetDescription(this Enum e)
{
FieldInfo field = e.GetType().GetField(e.ToString());
if (field != null)
{
object[] attribs =
field.GetCustomAttributes(typeof(DescriptionAttribute), false);

if (attribs.Length > 0)
return (attribs[0] as DescriptionAttribute).Description;
}
return e.ToString();
}
}



Y voila! A partir de este momento tendremos a nuestra disposición el método GetDescription(), que nos devolverá el texto asociado al elemento de la enumeración; si éste no existe, es decir, si no se ha decorado el elemento con el atributo apropiado, nos devolverá el identificador utilizado.

De esta forma eliminamos de un plumazo los dos inconvenientes citados anteriormente: la separación entre la definición de la enumeración y los textos descriptivos, y la necesidad de crear un conversor a texto por cada enumeración que usemos en nuestra aplicación.

Y por cierto, el equivalente en VB.NET completo sería:


Imports System.ComponentModel
Imports System.Reflection
Imports System.Runtime.CompilerServices

Module Module1
Public Enum EstadoProyecto
<Description(«Pendiente de aceptación»)> PendienteDeAceptacion
<Description(«En realización»)> EnRealizacion
<Description(«Finalizado»)> Finalizado
<Description(«Facturado y cerrado»)> FacturadoYCerrado
End Enum

<Extension()> _
Public Function GetDescription(ByVal e As System.Enum) As String
Dim field As FieldInfo = e.GetType().GetField(e.ToString())
If Not (field Is Nothing) Then
Dim attribs() As Object = _
field.GetCustomAttributes(GetType(DescriptionAttribute), False)
If attribs.Length > 0 Then
Return CType(attribs(0), DescriptionAttribute).Description
End If
End If
Return e.ToString()
End Function
End Module




Publicado en: www.variablenotfound.com.

5 comentarios sobre “Cómo describir los elementos de una enumeración usando métodos de extensión y atributos (C# y VB.NET)”

  1. Interesante técnica… solo le veo, a bote pronto, una pega… ¿cómo haríamos para poder mostrar la descripción en varios idiomas? ¿se te ocurre algo?

    Un saludo!

  2. Hola, Rodrigo.

    Así al vuelo, una posibilidad sería usar un atributo personalizado con el que decorar los elementos, que podría ser algo como:

    [LocalizedDescription(«Hello», «en-en»)]
    [LocalizedDescription(«Hola», «es-es»)]
    ElementoDeLaEnumeracion,
    […]

    De esta forma, el método de extensión GetDescription() podría buscar la descripción en función de la localización activa, por ejemplo, entre la colección de atributos de este tipo.

    Otra posibilidad sería incluir en el atributo la referencia al recurso de texto, de la misma forma en que lo haríamos con cualquier otra cadena constante en un contexto localizado. De nuevo GetDescription() sería el encargado de realizar la traducción.

    En fin, que posibilidades creo que haberlas haylas…

    Saludos.

  3. Hola.

    Muy interesante tu post, Sergio. Gracias por aportarlo.

    Espinete, mi opinión sobre lo que comentas.

    El «var» no rompe el tipado estricto de C# (o VB.Net), si es esto lo que te preocupa: simplemente hacen que sea el compilador el que decida el tipo en función de su valor de inicialización.

    Tampoco los métodos de extensión rompen ni amplían las clases: sólo hacen que sea más fácil llamar a un método que opere con un objeto de un tipo concreto.

    Tanto la inferencia de tipos como los métodos de extensión sólo introducen en el lenguaje una ayuda sintáctica para hacer cosas que podías hacer antes escribiendo un poco más de código. Sin embargo, ambas características eran absolutamente necesarias para dar soporte a novedades del lenguaje como linq o tipos anónimos.

    Por tanto, las mismas aberraciones contra la POO que puedas hacer usando extensiones o inferencia de tipos podías hacerlas antes también, por lo que no aportan, a mi entender, riesgo adicional.

    Eso sí, hay que usarlos con sentido común; por ejemplo, Microsoft en MSDN recomienda el uso de tipos de tipado implícito («var») sólo cuando sea necesario (por ejemplo para definir tipos anónimos), pues puede generar errores difíciles de detectar y depurar.

    De la misma forma, los métodos de extensión pueden crear confusión también al leer un código, pues es imposible, a simple vista, determinar si se trata de un método de instancia o no. Microsoft también recomienda su uso sólo en casos en los que no puedan utilizarse mecanismos habituales, como la herencia.

    (perdón por el rollo O:-))

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *