Enumeration (Enums) 2020
The .NET system is composed of classes, structures, enumerations, interfaces, and delegates.
Here we're going to see the role of the enumeration (enums).
When build a system, it is often convenient to create a set of symbolic names that map to known numerical values. For instance, if we are creating a payroll system, we may want to refer to the type of employees using constants such as JuniorEngineer, manager, SeniorEngineer, and VicePresident. C# enumeration comes into a play for this kind of situation. Here is the example:
enum EmployeeType { Manager, // 0 JuniorEngineer, // 1 SeniorEngineer, // 2 VicePresident // 3 }
The EmployeeType enumeration defines four names constants, corresponding to discrete numerical values. By default, the first element is set to zero, followed by an n+1 progression. We are free to change the initial values. So, we may have:
enum EmployeeType { Manager, // 101 JuniorEngineer, // 102 SeniorEngineer, // 103 VicePresident // 104 }
Enumerations do not necessarily need to follow a sequential order, and do not have to have unique values:
enum EmployeeType { Manager, // 101 JuniorEngineer, // 2 SeniorEngineer, // 13 VicePresident // 204 }
The storage type used to hold the values of an enumeration is, by default, a System.Int32 which is the C# int. However, we can change this. C# enumerations can be defined in a similar manner for any of the core system types such as byte, short, or long. For example, if we want to set the underlying storage value of EmployeeType to be a byte rather than an int, we can write the following:
enum EmployeeType : byte { Manager, // 0 JuniorEngineer, // 1 SeniorEngineer, // 2 VicePresident // 3 }
Changing the underlying type of an enumeration can be helpful if we are building a .NET application that will be deployed to a low-memory device and need to conserve memory.
Once we have established the range and storage type of our enumeration, we can use it in place of magic numbers. Because enumerations are nothing more than a user-defined type, we can use them as function return values, method parameters, local variables, and so on. In the following example, we have a method named AskForHike(), taking an EmployeeType variable as the parameter. Based on the value of the incoming parameter, we will print out a appropriate response to the pay hike request.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { enum EmployeeType : byte { Manager, // 0 JuniorEngineer, // 1 SeniorEngineer, // 2 VicePresident // 3 } static void AskForHike(EmployeeType e) { switch (e) { case EmployeeType.JuniorEngineer: Console.WriteLine("Junior Engineer"); break; case EmployeeType.SeniorEngineer: Console.WriteLine("Senior Engineer"); break; case EmployeeType.Manager: Console.WriteLine("Manager"); break; case EmployeeType.VicePresident: Console.WriteLine("Vice President"); break; } Console.WriteLine(); } static void Main(string[] args) { EmployeeType e = EmployeeType.SeniorEngineer; AskForHike(e); Console.ReadLine(); } } }
Output is:
Senior Engineer
Note that when we are assigning a value to an enum variable, we must scope the enum name (EmployeeType) to the value (SeniorEngineer). Because enumerations are a fixed set of name/value pairs, it is illegal to set an enum variable to a value that is not defined directly by the enumeration type:
static void Main(string[] args) { // Error - Recruiter is not in the EmployeeType enum. EmployeeType e = EmployeeType.Recruiter; // Error - Forgot to scope SeniorEngineer value to EmployeeType enum. e = SeniorEngineer; AskForHike(e); Console.ReadLine(); }
The .NET enumerations gain functionality from the System.Enum class type. This class defines a number of methods that allow us to interrogate and transform a given enumeration. One helpful method is the static Enum.GetUnderlyingType(). This returns the data type used to store the values of the enumeration type. For the previous example, it is System.Byte.
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { enum EmployeeType : byte { Manager, // 0 JuniorEngineer, // 1 SeniorEngineer, // 2 VicePresident // 3 } static void Main(string[] args) { EmployeeType e = EmployeeType.SeniorEngineer; Console.WriteLine("1: EmployeeType is using a {0} for storage", Enum.GetUnderlyingType(e.GetType())); Console.WriteLine("2: EmployeeType is using a {0} for storage", Enum.GetUnderlyingType(typeof(EmployeeType))); ); Console.ReadLine(); } } }
We get from the run:
1: EmployeeType is using a System.Byte for storage 2: EmployeeType is using a System.Byte for storage
If we were to consult the Visual Studio 2010 object browser, we would be able to verify that the Enum.GetUnderlyingType() method requires us to pass in a System.Type as the first parameter. Type represents the metadata description of a given .NET entity.
One way to obtain metadata is to use the GetType() method, which is common to all types in the .NET base class libraries. Another approach is to using the C# typeof operator. One benefit of using it is that we do not need to have a variable of the entity we want to obtain a metadata description of as shown in the example.
Beyond the Enum.GetUnderlyingType() method, all C# enumerations support ToString() method. It returns the string name of the current enumeration's value. If we are interested in discovering the value of a given enumeration variable, rather than its name, we can simply cast the enum variable against the underlying storage type. Let's take a look at the following example:
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { enum EmployeeType : byte { Manager, // 0 JuniorEngineer, // 1 SeniorEngineer, // 2 VicePresident // 3 } static void Main(string[] args) { EmployeeType e = EmployeeType.SeniorEngineer; Console.WriteLine("1: EmployeeType is a {0}.", e.ToString()); Console.WriteLine("2: {0} = {1}", e.ToString(), (byte)e); Console.ReadLine(); } } }
Output is:
1: EmployeeType is a SeniorEngineer. 2: SeniorEngineer = 2
System.enum also defines another static method GetValues(). This method returns an instance of
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyFirstCSharpCode { class Program { enum EmployeeType : byte { Manager, // 0 JuniorEngineer, // 1 SeniorEngineer, // 2 VicePresident // 3 } static void EvaluateEnum(System.Enum e) { Console.WriteLine("Underlying storage type: {0}", Enum.GetUnderlyingType(e.GetType())); Array enumArray = Enum.GetValues(e.GetType()); Console.WriteLine("This enum has {0} members.", enumArray.Length); for (int i = 0; i < enumArray.Length; i++) { Console.WriteLine("Name: {0}, Value: {0:D}", enumArray.GetValue(i)); } Console.WriteLine(); } static void Main(string[] args) { EmployeeType e = EmployeeType.SeniorEngineer;; DayOfWeek day = DayOfWeek.Thursday; ConsoleColor cc = ConsoleColor.White; EvaluateEnum(e); EvaluateEnum(day); EvaluateEnum(cc); Console.ReadLine(); } } }
Output from the run:
Underlying storage type: System.Byte This enum has 4 members. Name: Manager, Value: 0 Name: JuniorEngineer, Value: 1 Name: SeniorEngineer, Value: 2 Name: VicePresident, Value: 3 Underlying storage type: System.Int32 This enum has 7 members. Name: Sunday, Value: 0 Name: Monday, Value: 1 Name: Tuesday, Value: 2 Name: Wednesday, Value: 3 Name: Thursday, Value: 4 Name: Friday, Value: 5 Name: Saturday, Value: 6 Underlying storage type: System.Int32 This enum has 16 members. Name: Black, Value: 0 Name: DarkBlue, Value: 1 Name: DarkGreen, Value: 2 Name: DarkCyan, Value: 3 Name: DarkRed, Value: 4 Name: DarkMagenta, Value: 5 Name: DarkYellow, Value: 6 Name: Gray, Value: 7 Name: DarkGray, Value: 8 Name: Blue, Value: 9 Name: Green, Value: 10 Name: Cyan, Value: 11 Name: Red, Value: 12 Name: Magenta, Value: 13 Name: Yellow, Value: 14 Name: White, Value: 15
Enumerations are used extensively throughout the .NET base class libraries. For instance, ADO.NET is using numerous enumerations to represent the state of a database connection (such as opened, closed, etc.), the state of a row in a DataTable (changed, new, detached, etc.), and so on. So, when we use any enumeration, always remember that we can interact with the name/value pairs using the members of System.Enum.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization