Sunday, November 24, 2013

Generic Enum Attribute Caching

Attributes are wonderful for decorating your Enums with additional markup and meta data. However, looking up attributes via reflection is not always a fast operation in terms of performance. Additionally, no one likes typing that code over and over again.

Well not to worry, just use the following extension methods to help cache your Enum attribute look ups, and increase your application's performance! But how much faster is this? Good question...

Iterations Average Elapsed Ticks Difference
No Cache With Cache
2 1182.00 3934.00 4x Slower
10 20.10 7.10 3x Faster
100 13.07 1.37 10x Faster
1,000 13.27 1.40 10x Faster
10,000 13.56 1.45 10x Faster
100,000 13.02 1.33 10x Faster

Enum Extensions Code

public static class EnumExtensions
{
    private static readonly 
        ConcurrentDictionary<Tuple<bool, Type, Type, Enum>, Attribute> 
        AttributeMap = 
        new ConcurrentDictionary<Tuple<bool, Type, Type, Enum>, Attribute>(); 
 
    /// <summary>
    /// Gets the specified attribute from an enum via a cache.
    /// </summary>
    public static T GetAttribute<T>(this Enum value, bool inherit = true)
        where T : Attribute
    {
        var attrType = typeof(T);
        var valueType = value.GetType();
        var key = new Tuple<bool, Type, Type, Enum>(
            inherit, attrType, valueType, value);
 
        var attribute = AttributeMap.GetOrAdd(key, GetAttribute);
        return attribute as T;
    }
 
    private static Attribute GetAttribute(Tuple<bool, Type, Type, Enum> key)
    {
        var valueString = key.Item4.ToString();
        var field = key.Item3.GetField(valueString);
        return Attribute.GetCustomAttribute(field, key.Item2, key.Item1);
    }
 
    /// <summary>
    /// Returns a description of the enum if provided via a Description
    /// attribute. If not provided, the normal name is returned
    /// </summary>
    public static string Description(this Enum value)
    {
        return value.Description<DescriptionAttribute>();
    }
 
    /// <summary>
    /// Returns a description of the enum if provided via an extended
    /// Description attribute. If not provided, the normal name is returned
    /// </summary>
    public static string Description<T>(this Enum value)
        where T : DescriptionAttribute
    {
        var attribute = value.GetAttribute<T>();
        return attribute != null
            ? attribute.Description
            : value.ToString();
    }
}

Unit Tests

public class EnumExtensionsTests
{
    public class TestAttribute : Attribute
    {
        public string Message { get; set; }
        public TestAttribute(string message) { Message = message; }
    }
 
    public enum TestEnum
    {
        [Description("Hello world!")]
        HelloWorld = 1,
        [Test("Goodnight, moon?")]
        GoodnightMoon = 2
    }
 
    [Fact]
    public void DescriptionTest()
    {
        var helloWorld = TestEnum.HelloWorld.Description();
        Assert.Equal("Hello world!", helloWorld);
 
        var goodnightMoon = TestEnum.GoodnightMoon.Description();
        Assert.Equal("GoodnightMoon", goodnightMoon);
    }
 
    [Fact]
    public void GetAttributeTest()
    {
        var helloWorld = TestEnum.HelloWorld.GetAttribute<TestAttribute>();
        Assert.Null(helloWorld);
 
        var goodnightMoon = TestEnum.GoodnightMoon.GetAttribute<TestAttribute>();
        Assert.Equal("Goodnight, moon?", goodnightMoon.Message);
    }
}
Shout it

Enjoy,
Tom

No comments:

Post a Comment

Real Time Web Analytics