Geeks With Blogs

News

Let's Meet!

My Office Hours
profile for borrillis on Stack Exchange, a network of free, community-driven Q&A sites
Mathoms There and back again... a blogging story.

I happened to come across a situation where I needed an Enumeration but the enumeration needed to provide more than just a int value. In my situation, I not only had a integer value that was tied to the database, but I also needed to display an image for each individual value. I wanted to maintain a .Net-ish look and feel to the code, so I started by writing a few tests based on an existing enumeration. First the enum :

public enum StatusIndicator : int
{
    Ready = 1,
    Paused = 2,
    Offline = 3
}

And the tests :

[TestMethod]
public void GetPausedId()
{
    int expected = 2;
    int actual = (int)StatusIndicator.Paused;

    Assert.AreEqual( expected, actual );
}


[TestMethod]
public void GetPausedUrl()
{
    string expected = "status.paused.jpg";
    string actual = StatusIndicator.Paused.Image;

    Assert.AreEqual( expected, actual );
}

So obviously at this point the GetPausedImage() test fails because the enumeration doesn’t have a member named Image. The easiest way to do this on an existing enumeration without breaking any existing code is to use a static class and Extension Methods.

static class StatusIndicatorHelper
{
    private static readonly Dictionary<StatusIndicator, string> _image;

    static StatusIndicatorHelper()
    {
        _image = new Dictionary<StatusIndicator, string>();
        _image.Add( StatusIndicator.Ready, "status.ready.jpg" );
        _image.Add( StatusIndicator.Paused, "status.paused.jpg" );
        _image.Add( StatusIndicator.Offline, "status.offline.jpg" );
    }

    public static string Image( this StatusIndicator value)
    {
        return _image[ value ];
    }
}

However, even with this code, that test fails. It’s expecting Image as a Property, not a Method. Unfortunately, we can only create Extension Methods, not Extension Properties, so I ended up having to modify the test to use a method. This works, but maintenance looks like it could be disastrous.  If I add a new enumeration value, I have to add a line of initialization code to create a mapping between the value and it’s image. If I add a new attribute tot he enumeration, I have to add the container, it’s initialization code, and an extension method to get the attribute value for the enumeration.

Can this be made any better with Attributes? Let’s take a look at how I implemented it.

enum StatusIndicator
{
    [StatusIndicatorAttribute( "status.ready.jpg" )]
    Ready = 1,
    [StatusIndicatorAttribute( "status.paused.jpg" )]
    Paused = 2,
    [StatusIndicatorAttribute( "status.offline.jpg" )]
    Offline = 3
}

[AttributeUsage( AttributeTargets.Field, AllowMultiple = true )]
class StatusIndicatorAttribute : Attribute
{
    public StatusIndicatorAttribute( string image ) { this.Image = image; }
    public string Image { get; set; }

    public static StatusIndicatorAttribute Values( StatusIndicator value )
    {
        // get the list of fields in the enum
        FieldInfo field = typeof( StatusIndicator ).GetField( value.ToString() );
        object[] atts = field.GetCustomAttributes( typeof( StatusIndicatorAttribute ), false );

         // if we found 1, take a look at it
        if ( atts.Length > 0 )
        {
            // convert the first element to the right type (assume there is only 1 attribute)
            return (StatusIndicatorAttribute)atts[ 0 ];
        }

        return null;
    }
}

static class StatusIndicatorHelper
{
    public static string Image( this StatusIndicator value )
    {
        return StatusIndicatorAttribute.Values( value ).Image;
    }
}

This required modifying the enumeration, but in a non-breaking way. I also had to add two new classes, one for the attribute and another for the Extension Method helpers. The test code from the first implementation didn’t have to change at all, which is good. So how does this compare for maintenance? If I need to add a new enumeration value, I can just add the value into the enumeration and it’s associated attribute, usually a quick Copy+Paste+Modify operation. To add a new attribute, I would have to add a new Property to the StatusIndicatorAttribute class, and modify the constructor to accept the new attribute. Then I would have to add a new Extension Method to the StatusIndicatorHelper class to return the new attribute. Lastly, add the new attribute values onto existing enumeration values. A second way to add an attribute is a little more bloated, and that is implemented using a new Attribute class. I don’t recommend it as it causes code bloat in the Values() static method being in every new Attribute class.

This method caused me some concern due to the Reflection involved in getting the attribute value. This could be mitigated by caching the value in the Values() static method after retrieving it the first time, thereby reducing the Reflection calls to one per enumeration value. You could also control the timing of the reflection by using a static constructor to iterate over all the fields in the enumeration and cache the Attributes at that time.

The last implementation I came up with had the least .Net feel to it, in the implementation, but fit the requirements perfectly.

[Edit: 06/17/2009 - As Steve points out in the comments, this is the java type safe enumeration pattern. I didn't really 'come up' with this as much as I stumbled upon it. I also added the private constructor as I had missed that. ]

struct StatusIndicator
{
    public static readonly StatusIndicator Ready    = new StatusIndicator { Id = 1, Image = "status.ready.jpg" };
    public static readonly StatusIndicator Paused   = new StatusIndicator { Id = 2, Image = "status.paused.jpg" };
    public static readonly StatusIndicator Offline  = new StatusIndicator { Id = 3, Image = "status.offline.jpg" };

    #region Enumeration Implementation

    private StatusIndicator() { }

    public int Id { get; set; }
    public string Image { get; set; }

    public static explicit operator int( StatusIndicator value )
    {
        return value.Id;
    }

    #endregion Enumeration Implementation
}

In this implementation I am not using an enum at all. It has been replaced with a struct. In order to get the struct to act like an enum, I added an explicit conversion operator which return the Id property of the struct. This allows the GetPausedId() test to pass. In fact, the explicit type conversion in the test isn’t needed, the operator int() can be made implicit, but I felt that it was better to maintain compatibility rather than change behavior at this point.

Since this is a struct, I can add as many properties as I need. And it’s a simple process to add a new value to the enumeration.

After putting this all together, I am torn between the last two implementations. I like the .Net feel to the Attribute based implementation, but for high performance applications the reflection bothers me. However, for the struct based implementation, I like the improved maintenance story and lack of reflection, however the fact that is no longer a true enum is a little un-.Net like.

Posted on Tuesday, June 16, 2009 12:13 PM | Back to top


Comments on this post: Enumerations, structs and Extension Methods

# re: Enumerations, structs and Extension Methods
Requesting Gravatar...
That last solution -- apart from lacking the private or protected constructor -- is the classic pre-Java 5 enumeration pattern (e.g. http://www.bearcave.com/software/java/misl/enum/type_safe_enum.html).

Since System.Enum is one of the magic types that you can't use in a base-type constraint to a generic type parameter, you aren't losing anything obvious by this approach.
Left by Steve Gilham on Jun 17, 2009 1:56 AM

# re: Enumerations, structs and Extension Methods
Requesting Gravatar...
Steve,
Nice catch on the constructor, in production code I do use that, however missed it when putting together this post. You're absolutely correct about the origins of the last solution. I should have mentioned that somewhere. I came across the pattern while porting a Java web site for a client.
Left by Michael Cummings on Jun 17, 2009 10:42 AM

# re: Enumerations, structs and Extension Methods
Requesting Gravatar...
The problem is that while it passes your test cases, it fails many basic enum features. It cannot be used in a switch/case statement. In fact, even the following fails:

StatusIndicator ready =StatusIndicator.Ready;
if (ready == StatusIndicator.Ready) ...
Left by James Curran on Jun 24, 2009 8:11 AM

# re: Enumerations, structs and Extension Methods
Requesting Gravatar...
James,
Your right about the switch/case, but the second issue I don't see. The following tests pass in my test projects.

[TestMethod]
public void Assignment()
{
StatusIndicator expected = StatusIndicator.Ready;
StatusIndicator actual = StatusIndicator.Ready;

Assert.IsTrue( expected == actual );
}

[TestMethod]
public void Comparison()
{

StatusIndicator expected = StatusIndicator.Ready;

if ( expected == StatusIndicator.Ready )
{
Assert.IsTrue( true );
}
else
{
Assert.IsTrue( false );
}
}
Left by Michael Cummings on Jun 24, 2009 11:15 AM

# re: Enumerations, structs and Extension Methods
Requesting Gravatar...
Is there a reason that your final 'StatusIndicator' is a struct instead of a class? I am considering something like this but I would need to use a class because one of the members could be a reference type.
Left by David Boarman on Aug 03, 2011 2:18 PM

Your comment:
 (will show your gravatar)


Copyright © Michael Cummings | Powered by: GeeksWithBlogs.net | Join free