Accediendo Eficazmente a Atributos Personalizados en Valores de Enum en C#

Si estás desarrollando aplicaciones en C#, en ocasiones podrías encontrar la necesidad de obtener atributos personalizados asociados con valores de enums. Esta tarea puede parecer sencilla, especialmente para aquellos no familiarizados con la reflexión y la generación de código IL. Sin embargo, simplemente convertir los valores de enum a cadenas (sus nombres) puede llevar a problemas de rendimiento. En esta publicación de blog, exploraremos una forma limpia y eficiente de recuperar estos atributos sin la molestia de conversiones de cadenas.

Entendiendo el Problema

Comencemos con un breve ejemplo donde definimos un enum adornado con atributos personalizados:

public enum MyEnum {
    [CustomInfo("Este es un atributo personalizado")]
    None = 0,

    [CustomInfo("Este es otro atributo")]
    ValueA,

    [CustomInfo("Este tiene una bandera extra", AllowSomething = true)]
    ValueB,
}

En este fragmento, tenemos un enum llamado MyEnum, que contiene múltiples valores, cada uno emparejado con un atributo CustomInfo que detalla información adicional relacionada con cada miembro del enum.

El Desafío

El desafío principal surge cuando necesitamos recuperar estos atributos CustomInfo de una instancia del enum. El método inicial que viene a la mente es usar el siguiente código:

public CustomInfoAttribute GetInfo(MyEnum enumInput) {
    Type typeOfEnum = enumInput.GetType();
    FieldInfo fi = typeOfEnum.GetField(enumInput.ToString());
    return fi.GetCustomAttributes(typeof(CustomInfoAttribute), false).
        FirstOrDefault() as CustomInfoAttribute;
}

Si bien esta función recupera eficazmente el atributo, peca de ineficiencias de rendimiento debido al uso de enumInput.ToString(), que convierte el valor del enum a su representación de cadena.

Un Mejor Enfoque: Usando Generación de Código IL

Afortunadamente, existe un método más eficiente para acceder a atributos personalizados sin convertir enums a cadenas utilizando la generación de código IL. Al emplear métodos dinámicos y el ILGenerator, reducimos la sobrecarga y mejoramos el rendimiento. A continuación, describimos cómo crear un delegado que ayuda a acceder a propiedades rápidamente, lo cual se puede adaptar para la recuperación de atributos también.

Pasos para Implementar la Generación de Código IL

  1. Crear un Delegado: Define un delegado que tome un objeto y devuelva un objeto. Esto nos permitirá crear un getter de propiedad rápido.

    public delegate object FastPropertyGetHandler(object target);
    
  2. Emitir IL: Usando el ILGenerator, podemos generar un método IL que carga el objeto objetivo en la pila, llama al método getter y maneja correctamente el valor de retorno.

    private static void EmitBoxIfNeeded(ILGenerator ilGenerator, System.Type type) {
        if (type.IsValueType) {
            ilGenerator.Emit(OpCodes.Box, type);
        }
    }
    
  3. Obtener el Getter de Propiedad: Ahora, creamos un método para generar un método dinámico y construir el código IL necesario para crear un getter de propiedad rápido:

    public static FastPropertyGetHandler GetPropertyGetter(PropertyInfo propInfo) {
        DynamicMethod dynamicMethod = new DynamicMethod(
            string.Empty, 
            typeof(object), 
            new Type[] { typeof(object) },
            propInfo.DeclaringType.Module);
    
        ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetGetMethod(), null);
        EmitBoxIfNeeded(ilGenerator, propInfo.PropertyType);
        ilGenerator.Emit(OpCodes.Ret);
        FastPropertyGetHandler getter = (FastPropertyGetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertyGetHandler));
        return getter;
    }
    

Conclusión

Al implementar el método descrito anteriormente, puedes reducir eficazmente la sobrecarga asociada con la recuperación de atributos de valores de enum. No solo este enfoque elimina conversiones de cadena innecesarias, sino que también produce un rendimiento de tiempo de ejecución más rápido—esencial para aplicaciones que demandan eficiencia.

Incorporar estas técnicas en tus proyectos de C# te ayudará a navegar por atributos de enum sin problemas y permitirá un código más limpio y mantenible. ¡Feliz codificación!