Search
Close this search box.

C# Fundamentals: The Joys and Pitfalls of Enums

Continuing on the same vein as my last topic where I discussed differences between C# const and readonly keywords (here) and their uses, today I’m going to look at C# enums and their pitfalls.  I will only be discussing the basic enums today (the post got kind of long so I’ll discuss enums marked with the [Flags] attribute next post).

Quick Recap of Enums

First, a quick recap of enums for those who are brand new or less familiar with them.  Basically, enums a great way to declare a variable of a type that can be one of a fixed set of values.

If you didn’t use enums, you would probably set up a bunch of string or integer constants instead.  String comparison is, of course, more expensive than numeric comparison (though, of course, integers are harder to interpret through their raw values).

A sample set of int constants to represent an AccountType might look something like this:

 1: public static class AccountType
   2: {
   3:     public const int Checking = 0;
   4:     public const int Savings = 1;
   5:     public const int MoneyMarket = 2;
   6:     public const int IndividualRetirementAccount = 3;
   7:     public const int CertificateOfDeposit = 4;
   8: }

The problem is, since the type of these consts are int, declaring a variable to hold them gives you no hints as to what possible values it could have:

 1: public class Account
   2: {
   3:     public int Type { get; set; }
   4: }

Notice that if you just use integer constants, your variable holding the value is type int (or some other numeric).  Looking at this class alone, we have no idea what the possible values might be!  IntelliSense won’t help you either as it would only bring up members of object (since primitives are boxed as objects, you can call any object members off a primitive – unlike C++ and Java – such as: 5.ToString()).

There is also no way to really verify whether the value assigned to the int is valid other than to check against all possible defined consts for that domain manually, which can be a maintenance nightmare if they change often.

Thus enters the enum.  It allows you to define a series of values that the enumerated type can hold:

 1: public enum AccountType
   2: {
   3:     Checking,
   4:     Savings,
   5:     MoneyMarket,
   6:     IndividualRetirementAccount,
   7:     CertificateOfDeposit,
   8: }

This definition does two valuable things for us:

  • Defines compile-time constants for each of the enumerated values.
  • Creates a enumerated type that is strongly typed.

The first point simply means that C# will assign numeric values for the enumerated values.  For example, this definition is equivalent to the one above:

 1: public enum AccountType
   2: {
   3:     Checking = 0,
   4:     Savings = 1,
   5:     MoneyMarket = 2,
   6:     IndividualRetirementAccount = 3,
   7:     CertificateOfDeposit = 4,
   8: }

Note that if you don’t explicitly set values, the first value will default to zero and each subsequent value will be the next higher.  If you wish you can choose to only assign certain values, but this can lead to confusion:

1: public enum AnotherType
   2: {
   3:     One = 1,               // starts enum at one
   4:     Two,                   // next value increments from previous
   5:     Three,
   6:     Ten = 10,              // gives this value explicit 10
   7:     AnotherTen = 10,       // can even repeat a value if you like
   8:     Eleven,                // next value increments from previous
   9: }

In practice, though, unless you’re making an enum values match a set of domain values (and even then I recommend adapting between the domain values and your enum to reduce coupling) or unless you’re defining bit-flags (more on this in a later post), I don’t recommend manually assigning values – let C# do it for you.

The second point is important because it can save you from silly mistakes.  If you have an instance of AccountType, you can’t implicitly assign it a numeric or any other enumerated value:

1: AccountType value;
   2:  
   3: // compile time error, can’t implicitly convert int to AccountType
   4: value = 13;
   5:  
   6: // compile time error, can’t implicitly assign another values to this instance.
   7: value = AnotherType.One;
   8:  
   9: // the only thing that can be directly assigned are other instances of AccountType
  10: // or the AccountType values:
  11: value = AccountType.Checking;       // good, assigning an AccountType const value.
  12:  
  13: AccountType other = value;          // good, assigning one AccountType to another.

Also, because enums are strongly typed, you will get full IntelliSense when you are looking at the possible values.  For example, if you know a parameter is of type AccountType, all you need do is type in “AccountType.” and you will be presented with the list of AccountType values (Checking, Savings, etc).

Pitfall – Beware of Invalid Values

So they sound great, right?  Well yes, they are, but there are some things to watch out for.  First of all, while I said that you can’t implicitly convert an int to an enumerated value, you can explicitly request it (through a cast):

1: AccountType value = (AccountType) 65327;

This compiles, and yes it runs.  So what is the AccountType value?  Well, it is somewhat undefined.  That is, it actually quite happily accepts the value 65327 you forced on it (with a cast), but since this corresponds to none of the defined values, your value is really incorrect.  This is one of the other reasons I recommend avoiding casting int to enum and back. 

If you ever need to cast an int to an enum (or parse a string to an enum) you can check for validity by first calling Enum.IsDefined():

 1: AccountType accountTypeValue;
   2:  
   3: int value = int.Parse(Console.ReadLine());
   4:  
   5: if (Enum.IsDefined(typeof(AccountType), value)
   6: {
   7:     accountTypeValue = (AccountType)value;
   8: }

Same thing is true if you’re reading a string and want to parse it.  Note that if you want to use a string, it can either be a string representation of a number (e.g. “4”) or of the enumerated value identifier (e.g. “Checking”):

1: AccountType accountTypeValue;
   2:  
   3: string value = Console.ReadLine();
   4:  
   5: If (Enum.IsDefined(typeof(AccountType), value)
   6: {
   7:     // the third parameter is to ignore case (true) or be strict (false)
   8:     accountTypeValue = (AccountType)Enum.Parse(typeof(AccountType), value, true);
   9: }

Pitfall – Beware of Default Value (Zero)

While you can assign virtually any values to your enum, you should be careful to make sure there is always a valid zero value.  For example, take a look at this:

1: public enum OrderType
   2: {
   3:     Buy = 1,
   4:     Sell = 2
   5: }

Looks fine so far, right?  But then what if you had a class like this:

1: public class Order
   2: {
   3:     public OrderType TypeOfOrder { get; set; }
   4: }

If nothing in the constructor initializes TypeOfOrder, what OrderType value is it?  Is it the first value (Buy)?  Remember that all struct and class fields are automatically initialized with their default values.  For reference types, this is null, but for numeric types (of which enums are a specialization, of sorts), this is zero (0).

So, the answer is that (int)TypeOfOrder == 0 which is undefined in our set of values.  This can be very dangerous.  If switch statements processing our enum don’t have a default clause, we may never even see it till it really bites us!

So, we should always have a good zero value so that the enum value can be defaulted correctly.  But what should that value be?  Consider if we would have accepted the default numbering:

1: public enum OrderType
   2: {
   3:     Buy,     // zero since unspecified
   4:     Sell     // 1
   5: }

Ah, this is better, somewhat…  but now consider that Order constructor again.  This means that every time we create an Order object it will default to TypeOfOrder == OrderType.Buy.  While this is better than being undefined, it may be a logically incorrect assumption. 

So, in those cases where you can’t assume that the first enum value (zero value) is the default, you should either create an Unknown or consider using a nullable (System.Nullable<T>enum (more on this in a later post, essentially this lets you make value types optional). 

So, if we wanted to use an Unknown:

1: public enum OrderType
   2: {
   3:     Unknown,
   4:     Buy,
   5:     Sell
   6: }

Now, an Order in its default constructed state will have TypeOfOrder == OrderType.Unknown which fits logically (since we haven’t assigned it).

Pitfall – Enum Values are Compile-Time Constants

In my last post (here), I talked about the difference between const and readonly and how const is a compile-time constant and the pitfalls of it.  Well, enum value definitions are compile-time constant as well!  This means that if you declare your enum publically in a class library and then later change the values, and assemblies that use those values need to recompile as well or they will use the version of the value they were compiled with! 

As a quick example, let’s assume a class library named Ordering.DLL is created and has this enum and a method that uses it:

 1: // sample enum to illustrate compile-time const-ness of values
   2: public enum Ordering
   3: {
   4:          // int value == 0
   5:          First,
   6:          // int value == 1
   7:          Second,
   8:          // int value == 2
   9:          Third,
  10: }
  11:  
  12: public static class PotentiallyConstant
  13: {
  14:          // given an ordering value, print it's string name and integer value.
  15:          public static string WhatOrderAmI(Ordering myOrder)
  16:          {
  17:                  return string.Format("{0} [{1}]", myOrder, (int)myOrder);
  18:          }
  19: }
  20:  

Now let’s assume that another assembly named OrderProcessor.EXE consumes it:

1: public static class Program
   2: {
   3:          public static void Main()
   4:          {
   5:                  // grab the first part of the enum, since the enum is defined and used by the other assembly, this is fine.
   6:                  Console.WriteLine("This should be second place: {0} [{1}] - {2}",
                                 Ordering.Second, (int)Ordering.Second, 
                                 PotentiallyConstant.WhatOrderAmI(Ordering.Second));
   7:          }
   8: } 

We would expect after compiling this that we would get the following output:

1: This should be second place: Second [1] – Second [1]. 

The first value is output from Program.EXE, and the second is generated from the class library.  Now let’s say we go in and tweak our enum value to add a new value called None and put it at the front of the enum: // sample enum to illustrate compile-time const-ness of values.

 1: public enum Ordering
   2: {
   3:          // int value == 0
   4:          None,
   5:          // int value == 1
   6:          First,
   7:          // int value == 2
   8:          Second,
   9:          // int value == 3
  10:          Third,
  11: } 

Notice that this has bumped all the existing values of the enum up by one.  Now, if we just deployed the new class library without recompiling everyone who uses it, we’d see this:

   1: This should be second place: First [1] – First [1]. 

See what’s happened?  Even though we used Ordering.Second in our program, at the time it was compiled, Ordering.Second had the value 1.  We recompiled the library with the enum definition so that now Ordering.First has the value 1, and thus the erroneous output. 

You may wonder why it output the correct name for the value 1 in the program even though the program was not recompiled.   This is because even though Ordering.Second was compiler-replaced with 1 in the program, interpretation of the values of the enum (parsing, converting to string) is still part of the enum itself and thus it stays correct. In other words, the enum const values (Ordering.First, Ordering.Second, etc) when used explicitly in a program are replaced at compile-time with their numeric values.

The solution?  Well, there’s two things really.  First of all, if you want to modify an enum, you should probably try to always add to the end of the enum and not re-order the contents or change existing values.  If you must change or reorder the values, make sure all assemblies that use that enum are recompiled correctly.

Summary

Enums are a powerful tool that lets you simplify development by providing a range of values that a variable can be.  This both makes your code more readable and aids the user by supplying them readily with the list of values the variable can be.   There are a few “gotchas” to watch out for, but in general as long as you avoid casting int to enum (or at least check them with Enum.IsDefined()), make sure you have a good default value, and avoid re-ordering enum values (or rebuilding completely if you do), you should be fine!

Stay tuned next time when I dive into what exactly the [Flags] attribute does to the enum definition and, more importantly, what it doesn’t do…

Print | posted on Thursday, July 08, 2010 5:53 PM | Filed Under [ My BlogC#Software.NETFundamentals ]

This article is part of the GWB Archives. Original Author: James Michael Hare

Related Posts