C#/.NET Little Wonders: Fun With Enum Methods

Posted by James Michael Hare on Geeks with Blogs See other posts from Geeks with Blogs or by James Michael Hare
Published on Thu, 09 Dec 2010 23:50:43 GMT Indexed on 2010/12/10 22:19 UTC
Read the original article Hit count: 550

Filed under:

Once again lets dive into the Little Wonders of .NET, those small things in the .NET languages and BCL classes that make development easier by increasing readability, maintainability, and/or performance.

So probably every one of us has used an enumerated type at one time or another in a C# program.  The enumerated types we create are a great way to represent that a value can be one of a set of discrete values (or a combination of those values in the case of bit flags).

But the power of enum types go far beyond simple assignment and comparison, there are many methods in the Enum class (that all enum types “inherit” from) that can give you even more power when dealing with them.

IsDefined() – check if a given value exists in the enum

Are you reading a value for an enum from a data source, but are unsure if it is actually a valid value or not?  Casting won’t tell you this, and Parse() isn’t guaranteed to balk either if you give it an int or a combination of flags.  So what can we do?

Let’s assume we have a small enum like this for result codes we want to return back from our business logic layer:

   1: public enum ResultCode
   2: {
   3:     Success,
   4:     Warning,
   5:     Error
   6: }

In this enum, Success will be zero (unless given another value explicitly), Warning will be one, and Error will be two.

So what happens if we have code like this where perhaps we’re getting the result code from another data source (could be database, could be web service, etc)?

   1: public ResultCode PerformAction()
   2: {
   3:     // set up and call some method that returns an int.
   4:     int result = ResultCodeFromDataSource();
   5:  
   6:     // this will suceed even if result is < 0 or > 2.
   7:     return (ResultCode) result;
   8: }

So what happens if result is –1 or 4?  Well, the cast does not fail, so what we end up with would be an instance of a ResultCode that would have a value that’s outside of the bounds of the enum constants we defined.

This means if you had a block of code like:

   1: switch (result)
   2: {
   3: case ResultType.Success:
   4:     // do success stuff
   5:     break;
   6:  
   7: case ResultType.Warning:
   8:     // do warning stuff
   9:     break;
  10:  
  11: case ResultType.Error:
  12:     // do error stuff
  13:     break;
  14: }

That you would hit none of these blocks (which is a good argument for always having a default in a switch by the way).

So what can you do?  Well, there is a handy static method called IsDefined() on the Enum class which will tell you if an enum value is defined. 

   1: public ResultCode PerformAction()
   2: {
   3:     int result = ResultCodeFromDataSource();
   4:  
   5:     if (!Enum.IsDefined(typeof(ResultCode), result))
   6:     {
   7:         throw new InvalidOperationException("Enum out of range.");
   8:     }
   9:  
  10:     return (ResultCode) result;
  11: }

In fact, this is often recommended after you Parse() or cast a value to an enum as there are ways for values to get past these methods that may not be defined.

If you don’t like the syntax of passing in the type of the enum, you could clean it up a bit by creating an extension method instead that would allow you to call IsDefined() off any isntance of the enum:

   1: public static class EnumExtensions
   2: {
   3:     // helper method that tells you if an enum value is defined for it's enumeration
   4:     public static bool IsDefined(this Enum value)
   5:     {
   6:         return Enum.IsDefined(value.GetType(), value);
   7:     }
   8: }

 

HasFlag() – an easier way to see if a bit (or bits) are set

Most of us who came from the land of C programming have had to deal extensively with bit flags many times in our lives.  As such, using bit flags may be almost second nature (for a quick refresher on bit flags in enum types see one of my old posts here).

However, in higher-level languages like C#, the need to manipulate individual bit flags is somewhat diminished, and the code to check for bit flag enum values may be obvious to an advanced developer but cryptic to a novice developer.

For example, let’s say you have an enum for a messaging platform that contains bit flags:

   1: // usually, we pluralize flags enum type names
   2: [Flags]
   3: public enum MessagingOptions
   4: {
   5:     None = 0,
   6:     Buffered = 0x01,
   7:     Persistent = 0x02,
   8:     Durable = 0x04,
   9:     Broadcast = 0x08
  10: }

We can combine these bit flags using the bitwise OR operator (the ‘|’ pipe character):

   1: // combine bit flags using 
   2: var myMessenger = new Messenger(MessagingOptions.Buffered | MessagingOptions.Broadcast);

Now, if we wanted to check the flags, we’d have to test then using the bit-wise AND operator (the ‘&’ character):

   1: if ((options & MessagingOptions.Buffered) == MessagingOptions.Buffered)
   2: {
   3:     // do code to set up buffering...
   4:     // ...
   5: }

While the ‘|’ for combining flags is easy enough to read for advanced developers, the ‘&’ test tends to be easy for novice developers to get wrong.  First of all you have to AND the flag combination with the value, and then typically you should test against the flag combination itself (and not just for a non-zero)! 

This is because the flag combination you are testing with may combine multiple bits, in which case if only one bit is set, the result will be non-zero but not necessarily all desired bits!

Thanks goodness in .NET 4.0 they gave us the HasFlag() method.  This method can be called from an enum instance to test to see if a flag is set, and best of all you can avoid writing the bit wise logic yourself.  Not to mention it will be more readable to a novice developer as well:

   1: if (options.HasFlag(MessagingOptions.Buffered))
   2: {
   3:     // do code to set up buffering...
   4:     // ...
   5: }

It is much more concise and unambiguous, thus increasing your maintainability and readability.

It would be nice to have a corresponding SetFlag() method, but unfortunately generic types don’t allow you to specialize on Enum, which makes it a bit more difficult.  It can be done but you have to do some conversions to numeric and then back to the enum which makes it less of a payoff than having the HasFlag() method. 

But if you want to create it for symmetry, it would look something like this:

   1: public static T SetFlag<T>(this Enum value, T flags)
   2: {
   3:     if (!value.GetType().IsEquivalentTo(typeof(T)))
   4:     {
   5:         throw new ArgumentException("Enum value and flags types don't match.");
   6:     }
   7:  
   8:     // yes this is ugly, but unfortunately we need to use an intermediate boxing cast
   9:     return (T)Enum.ToObject(typeof (T), Convert.ToUInt64(value) | Convert.ToUInt64(flags));
  10: }

Note that since the enum types are value types, we need to assign the result to something (much like string.Trim()).  Also, you could chain several SetFlag() operations together or create one that takes a variable arg list if desired.

Parse() and ToString() – transitioning from string to enum and back

Sometimes, you may want to be able to parse an enum from a string or convert it to a string - Enum has methods built in to let you do this.  Now, many may already know this, but may not appreciate how much power are in these two methods.

For example, if you want to parse a string as an enum, it’s easy and works just like you’d expect from the numeric types:

   1: string optionsString = "Persistent";
   2:  
   3: // can use Enum.Parse, which throws if finds something it doesn't like...
   4: var result = (MessagingOptions)Enum.Parse(typeof (MessagingOptions), optionsString);
   5:  
   6: if (result == MessagingOptions.Persistent)
   7: {
   8:     Console.WriteLine("It worked!");
   9: }

Note that Enum.Parse() will throw if it finds a value it doesn’t like.  But the values it likes are fairly flexible!  You can pass in a single value, or a comma separated list of values for flags and it will parse them all and set all bits:

   1: // for string values, can have one, or comma separated.
   2: string optionsString = "Persistent, Buffered";
   3:  
   4: var result = (MessagingOptions)Enum.Parse(typeof (MessagingOptions), optionsString);
   5:  
   6: if (result.HasFlag(MessagingOptions.Persistent) && result.HasFlag(MessagingOptions.Buffered))
   7: {
   8:     Console.WriteLine("It worked!");
   9: }

Or you can parse in a string containing a number that represents a single value or combination of values to set:

   1: // 3 is the combination of Buffered (0x01) and Persistent (0x02)
   2: var optionsString = "3";
   3:  
   4: var result = (MessagingOptions) Enum.Parse(typeof (MessagingOptions), optionsString);
   5:  
   6: if (result.HasFlag(MessagingOptions.Persistent) && result.HasFlag(MessagingOptions.Buffered))
   7: {
   8:     Console.WriteLine("It worked again!");
   9: }

And, if you really aren’t sure if the parse will work, and don’t want to handle an exception, you can use TryParse() instead:

   1: string optionsString = "Persistent, Buffered";
   2: MessagingOptions result;
   3:  
   4: // try parse returns true if successful, and takes an out parm for the result
   5: if (Enum.TryParse(optionsString, out result))
   6: {
   7:     if (result.HasFlag(MessagingOptions.Persistent) && result.HasFlag(MessagingOptions.Buffered))
   8:     {
   9:         Console.WriteLine("It worked!");
  10:     }
  11: }

So we covered parsing a string to an enum, what about reversing that and converting an enum to a string?  The ToString() method is the obvious and most basic choice for most of us, but did you know you can pass a format string for enum types that dictate how they are written as a string?:

   1: MessagingOptions value = MessagingOptions.Buffered | MessagingOptions.Persistent;
   2:  
   3: // general format, which is the default, 
   4: Console.WriteLine("Default    : " + value);
   5: Console.WriteLine("G (default): " + value.ToString("G"));
   6:  
   7: // Flags format, even if type does not have Flags attribute.
   8: Console.WriteLine("F (flags)  : " + value.ToString("F"));
   9:  
  10: // integer format, value as number.
  11: Console.WriteLine("D (num)    : " + value.ToString("D"));
  12:  
  13: // hex format, value as hex
  14: Console.WriteLine("X (hex)    : " + value.ToString("X"));

Which displays:

   1: Default    : Buffered, Persistent
   2: G (default): Buffered, Persistent
   3: F (flags)  : Buffered, Persistent
   4: D (num)    : 3
   5: X (hex)    : 00000003

Now, you may not really see a difference here between G and F because I used a [Flags] enum, the difference is that the “F” option treats the enum as if it were flags even if the [Flags] attribute is not present.  Let’s take a non-flags enum like the ResultCode used earlier:

   1: // yes, we can do this even if it is not [Flags] enum.
   2: ResultCode value = ResultCode.Warning | ResultCode.Error;

And if we run that through the same formats again we get:

   1: Default    : 3
   2: G (default): 3
   3: F (flags)  : Warning, Error
   4: D (num)    : 3
   5: X (hex)    : 00000003

Notice that since we had multiple values combined, but it was not a [Flags] marked enum, the G and default format gave us a number instead of a value name.  This is because the value was not a valid single-value constant of the enum.  However, using the F flags format string, it broke out the value into its component flags even though it wasn’t marked [Flags].

So, if you want to get an enum to display appropriately for whether or not it has the [Flags] attribute, use G which is the default.  If you always want it to attempt to break down the flags, use F.  For numeric output, obviously D or  X are the best choice depending on whether you want decimal or hex.

Summary

Hopefully, you learned a couple of new tricks with using the Enum class today!  I’ll add more little wonders as I think of them and thanks for all the invaluable input!

 

© Geeks with Blogs or respective owner