Andy's observations as he continues to attempt to know all that is .NET...

Friday, February 02, 2007

Custom ToString() for Flag based Enums, and a splatter of Unit Testing

A project that I'm currently working on has a enum type, where the enum is defined like so

[Flags]

public
enum EventDayMask

{

NONE = 0,

SUNDAY = 1,

MONDAY = 2,

TUESDAY = 4,

WEDNESDAY = 8,

THURSDAY = 16,

FRIDAY = 32,

SATURDAY = 64

}


 

Calling ToString on a value of type EventDayMask would result in a comma separated list of the various set bits..

EventDayMask weekend = EventDayMask.SATURDAY | EventDayMask.SUNDAY;


 

Console.WriteLine( weekend.ToString() );


 

Would produce SATURDAY,SUNDAY..Whilst this is a massive improvement on what we had with C and C++ I in fact would like a more appealing string like Saturday, Sunday.

You can't override ToString for Enum's which means you can't write your custom string generate as part of the enum class. There are many blog posts on how you can achieve this for a non Flagged based enums, by applying custom attributes to your enum definition and then having a static method on a Utils class that first determines the value of the enum and then determines if there is a custom attribute associated with that value and if so uses the string associated with that custom attribute.

E.g

public
enum EventDayMask

{

NONE = 0,

[Description("Sunday")]

SUNDAY = 1,

[Description("Monday")] // etc..

MONDAY = 2,

TUESDAY = 4,

WEDNESDAY = 8,

THURSDAY = 16,

FRIDAY = 32,

SATURDAY = 64

}


 

However with flag based enum's it gets a bit more complex, since an enum does not have a single value but a combination of many valid values, as in the DayMask example above. To extend the technique to Flag'd based enums you need to effectively test the enum value against each possible flag value. I managed to write a version that did this by stepping through each possible value and performing a logical "AND" against the underlying enum value, and that worked fine, until I added an or'd value to my enum definition.

public enum EventDayMask

{

    ...

[Description("Weekend")]

    WEEKEND = SATURDAY | SUNDAY

}


 

The built in ToString() works as expected if the underlying value is SATURDAY | SUNDAY it outputs WEEKEND. Obviously I wanted the same behaviour, it was at this point that I realised this small task was now going to escalate, a quick cup of tea latter and I decided to change my approach why not simply use the built in ToString() to generate the initial string and then replace each of its component parts with the value stored in the attribute. This simplified the code greatly..the downside being that I'm tightly coupled with the output format of Enum.ToString(). To counteract this I have a handful of unit tests as part of the project that tests this functionality, so if MS ever changed the formatting algorithm for Enum.ToString(), I'm alerted immediately..This for me is another example why unit testing is so powerful, I can make expedient decisions that I wouldn't have dared made without them....


 

[AttributeUsage(AttributeTargets.Field,AllowMultiple=false)]


public
class
EnumValueDescriptionAttribute : Attribute

{


public EnumValueDescriptionAttribute(string description)

{

Description = description;

}


 


public
string Description;

}


 


public
static
class
EnumUtils

{


private
const
char ENUM_FLAGGED_VALUE_SEPERATOR_CHARACTER = ',';


 


public
static
string EnumToString(Enum enumValue)

{


StringBuilder enumValueAsString = new
StringBuilder();


 


Type enumType = enumValue.GetType();


 


string[] enumToStringParts = enumValue.ToString().Split(ENUM_FLAGGED_VALUE_SEPERATOR_CHARACTER);


 


foreach (string enumValueStringPart in enumToStringParts)

{


FieldInfo enumValueField = enumType.GetField(enumValueStringPart.Trim());


 


EnumValueDescriptionAttribute[] enumDesc = enumValueField.GetCustomAttributes(typeof(EnumValueDescriptionAttribute), false) as
EnumValueDescriptionAttribute[];


 


if (enumValueAsString.Length > 0)

{

enumValueAsString.Append(ENUM_FLAGGED_VALUE_SEPERATOR_CHARACTER);

}


 


if (enumDesc.Length == 1)

{

enumValueAsString.Append(enumDesc[0].Description);

}


else

{

enumValueAsString.Append(enumValueStringPart);

}

}


 


return enumValueAsString.ToString();

}

}

No comments:

About Me

My photo
Im a freelance consultant for .NET based technology. My last real job, was at Cisco System were I was a lead architect for Cisco's identity solutions. I arrived at Cisco via aquisition and prior to that worked in small startups. The startup culture is what appeals to me, and thats why I finally left Cisco after seven years.....I now filll my time through a combination of consultancy and teaching for Developmentor...and working on insane startups that nobody with an ounce of sense would look twice at...