Enum - Expert C# 5.0: with .NET 4.5 Framework (2013)

Expert C# 5.0: with .NET 4.5 Framework (2013)

CHAPTER 6

images

Enum

This chapter will discuss one of the nice features of the C# language: enumerated type or enum for short. I will show you how the enum type is defined in the .NET Framework, how you can get symbolic names and values from the enum, and how enum parses in C#.

Enum and .NET

Enum in C# is a type, which is a set of symbolic names and values (given by you or the C# compiler) pair. For example:

public enum Planets
{
Sun = 0,
Earth,
}

Or with the explicit type:

public enum Planets : int /* int is the underlying type which C# compiler will use to set
* the values */
{ /* for each of the symbolic names such as Sun, Earth. */
Sun = 0,
Earth,
}

Or you can also define it as shown here, which is the most common way to declare an enum:

public enum Planets
{
Sun, /* The C# compiler sets the values */
Earth,
}

Planets is an Enum type, which contains two symbolic names—Sun and Earth—with value 0 (given) and 1 (assigned by the C# compiler). You can use this Planets Enum as shown in this example:

Planets whereYouLive = Planets.Earth; /* assign Earth to the whereYouLive variable */
bool liveInEarth = whereYouLive == Planets.Earth; /* produced true as result */

Enumerated type is derived from the System.Enum, as shown in Figure 6-1.

images

Figure 6-1. Enum in.NET

The Enum type has been defined in the System namespace of the mscorlib.dll (located in C:\WINDOWS\ Microsoft.NET\Framework\v4.0.30319\mscorlib.dll), which is derived from System.ValueType, as shown in Figure 6-2.

images

Figure 6-2. System.Enum in .NET Framework

The class definition of the Enum would be:

public abstract class Enum : ValueType, IComparable, IFormattable, IConvertible

Listing 6-1 presents an example of an enum to show how .NET deals with the Enum type.

Listing 6-1. An Example of Planets Enum

namespace Ch06
{
class Program
{
public enum Planets
{
Sun = 0, /* Otherwise compiler will assign default value */
Mercury, /* C# compiler will assign 1 */
Venus, /* C# compiler will assign 2 */
Earth, /* C# compiler will assign 3 */
Mars, /* C# compiler will assign 4 */
Jupiter, /* C# compiler will assign 5 */
Saturn, /* C# compiler will assign 6 */
Uranus, /* C# compiler will assign 7 */
Neptune /* C# compiler will assign 8 */
}

static void Main(string[] args)
{
Planets planets = new Planets();
planets = Planets.Earth;
}
}
}

In Listing 6-1, an Enum type Planets has been defined that has nine symbolic names with an initial value of 0 for the first item. You do not need to set the initial value for the Enum item explicitly; the C# compiler can take care of that. Listing 6-1 will be compiled and then the C# compiler will assign a value for the rest of the items in the Planets Enum, as the initial value for the first item has already been given. Listing 6-2 shows the decompiled IL code for Listing 6-1 (decompiled using the .Net Reflector tool), which will show how the C# compiler makes each of the items of the Enumstatic and assigns a value to it.

Listing 6-2. Decompiled IL Code of the Planets Enum

.class auto ansi sealed nested public Planets
extends [mscorlib]System.Enum
{
.field public static literal valuetype Ch06.Program/Planets Sun = int32(0)
.field public static literal valuetype Ch06.Program/Planets Mercury = int32(1)
.field public static literal valuetype Ch06.Program/Planets Venus = int32(2)
.field public static literal valuetype Ch06.Program/Planets Earth = int32(3)
.field public static literal valuetype Ch06.Program/Planets Mars = int32(4)
.field public static literal valuetype Ch06.Program/Planets Jupiter = int32(5)
.field public static literal valuetype Ch06.Program/Planets Saturn = int32(6)
.field public static literal valuetype Ch06.Program/Planets Uranus = int32(7)
.field public static literal valuetype Ch06.Program/Planets Neptune = int32(8)
.field public specialname rtspecialname int32 value__
}

From Listing 6-2 you can see that Planets Enum has been derived from the System.Enum class and the compiler assigned each of the items in the enum a value of type int32 (default type given by the C# compiler), where the value started at 0 and then incremented unless otherwise defined, such as shown in Listing 6-3.

Listing 6-3. Value Assigned for the Enum Items

public enum Planets
{
Sun = 10, /* C# compiler will assign 10 */
Mercury = 12, /* C# compiler will assign 12 */
Venus = 14, /* C# compiler will assign 14 */
Earth = 16, /* C# compiler will assign 16 */
Mars = 20, /* C# compiler will assign 20 */
Jupiter = 24, /* C# compiler will assign 24 */
Saturn = 32, /* C# compiler will assign 32 */
Uranus = 16, /* C# compiler will assign 16 */
Neptune = 99 /* C# compiler will assign 99 */
}

Let’s decompile Listing 6-3 using the .Net Reflector tool, which shows that the C# compiler assigned the given value for each of the Enum items, as shown in Listing 6-4. For example, 10 for the Sun, 12 for the Mercury, and so on. You don’t need to set values for the enum item unless it’s required for your application.

Listing 6-4. Decompiled IL Code of Listing 6-3 using the .Net Reflector Tool

.class auto ansi sealed nested public Planets
extends [mscorlib]System.Enum
{
.field public static literal valuetype Ch06.Program/Planets Sun = int32(10)
.field public static literal valuetype Ch06.Program/Planets Mercury = int32(12)
.field public static literal valuetype Ch06.Program/Planets Venus = int32(14)
.field public static literal valuetype Ch06.Program/Planets Earth = int32(0x10)
.field public static literal valuetype Ch06.Program/Planets Mars = int32(20)
.field public static literal valuetype Ch06.Program/Planets Jupiter = int32(0x18)
.field public static literal valuetype Ch06.Program/Planets Saturn = int32(0x20)
.field public static literal valuetype Ch06.Program/Planets Uranus = int32(0x10)
.field public static literal valuetype Ch06.Program/Planets Neptune = int32(0x63)
.field public specialname rtspecialname int32 value__
}

If you dive in to find out more about the enum, you will find that:

· The C# compiler adds the given Enum values to the Constant section of the Metadata Info of the executable file. Each of the constant items from the Constant section will be linked with the Blob section of the Metadata Info, as shown in Figure 6-3. For example, the first row of the Constantsection (1) refers to the blob#1b of the Blob section; the second row of the Constant section (2) refers to the blob#20 of the Blob section, and so on.

· Each of the blob items will contain a value for the enum item, for example, blob item 1b contains 0x0a, which is 10 in decimal for the Sun, blob item 20 contains 0x0c, which is 12 in decimal for Mercury, and so on.

Figure 6-3 demonstrates the Metadata Info relating to the enum in C# for the Enum defined in Listing 6-3.

images

Figure 6-3. Enum value in the Metadata Info

How to Get Names and Values from the Enum

When you define an enum, you might need to get all the names or the values defined in the enum. The program in Listing 6-5 shows how to get all of the names from the enum.

Listing 6-5. Get Names of the Enum

using System;
using System.Linq;

namespace Ch06
{
class Program
{
public enum Planets
{
Sun = 0, /* Otherwise compiler will assign default value */
Mercury, /* compiler will assign 1 */
Venus, /* compiler will assign 2 */
Earth, /* compiler will assign 3 */
Mars, /* compiler will assign 4 */
Jupiter, /* compiler will assign 5 */
Saturn, /* compiler will assign 6 */
Uranus, /* compiler will assign 7 */
Neptune /* compiler will assign 8 */
}

static void Main(string[] args)
{
Enum.GetNames(typeof(Planets)).ToList().ForEach(name => Console.Write(name + "\t"));
}
}
}

The program in Listing 6-5 will produce the following output:

Sun Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune

Let’s find out how the GetNames works internally in C#.

The first step is where the GetNames method internally calls the GetEnumNames method of the RuntimeType class:

.method public hidebysig static string[] GetNames(class System.Type enumType) cil managed
{
/* Code removed */
L_0015: callvirt instance string[] System.Type::GetEnumNames()
L_001a: ret
}

In addition, the GetEnumNames method internally calls the InternalGetNames method to retrieve the names from the given Enum and returns it as an array of string, as demonstrated in the following implementation of the GetEnumNames method:

public override string[] GetEnumNames()
{
string[] names = Enum.InternalGetNames(this); /* Get all the symbolic names define in the
given Enum. */
string[] destinationArray = new string[names.Length];
Array.Copy(names, destinationArray, names.Length);
return destinationArray;
}

The InternalGetNames method calls the GetHashEntry method to get the data. This method wraps all the names and associated values into a HashEntry object. The HashEntry class has been defined in the Enum class, as shown in Figure 6-4.

images

Figure 6-4. HashEntry Class of the Enum

The implementation of the HashEntry class is:

private class HashEntry
{
public string[] names; /* Used to hold the Symbolic name defined in a Enum */
public ulong[] values; /* Used to hold the associate values for the symbolic names
* defined in a Enum */

public HashEntry(string[] names, ulong[] values)
{
this.names = names;
this.values = values;
}
}

The implementation of the GetHashEntry method would be:

private static HashEntry GetHashEntry(RuntimeType enumType)
{
HashEntry entry = (HashEntry) fieldInfoHash[enumType];
/*Code removed*/
ulong[] o = null;
string[] strArray = null;
GetEnumValues(
enumType.GetTypeHandleInternal(),
JitHelpers.GetObjectHandleOnStack<ulong[]>(ref o),
JitHelpers.GetObjectHandleOnStack<string[]>(ref strArray));
entry = new HashEntry(strArray, o);
fieldInfoHash[enumType] = entry;
return entry;
}

The GetHashEntry method returns the instance of the HashEntry, which holds symbolic names and values. Finally, GetEnumNames method returns the names’ array from the HashEntry object.

Listing 6-6 shows that Listing 6-5 has been modified to get only the values from the given Enum.

Listing 6-6. GetValues from the Enum

using System;
using System.Linq;

namespace Ch06
{
class Program
{
public enum Planets
{
Sun = 0, /* Otherwise compiler will assign default value */
Mercury, /* compiler will assign 1 */
Venus, /* compiler will assign 2 */
Earth, /* compiler will assign 3 */
Mars, /* compiler will assign 4 */
Jupiter, /* compiler will assign 5 */
Saturn, /* compiler will assign 6 */
Uranus, /* compiler will assign 7 */
Neptune /* compiler will assign 8 */
}

static void Main(string[] args)
{
Enum.GetValues(typeof(Planets)).Cast<int>().ToList().ForEach(
name => Console.Write(name + "\t"));
}
}
}

This program will produce the following output:

0 1 2 3 4 5 6 7 8

The CLR will call the GetValues method of the Enum class to retrieve all the values from the given Enum. The implementation of the GetValues is shown below:

public static Array GetValues(Type enumType)
{
return enumType.GetEnumValues();
}

The GetValues method calls the GetEnumValues method of the RuntimeType class. Internally this GetEnumValues method will call the InternalGetValues method of the Enum class. InternalGetValues will call the GetHashEntry method in the same way it works for the GetNames method described above, except this method will return values instead of names as output from the HashEntry instance returned from the GetEnumValues method.

Determining Whether an Item Is Defined

In many circumstances, we need to find out if a particular item is defined in an enumerated type. For example, the code in Listing 6-7 shows Jupiter is defined in the Planets enum.

Listing 6-7. Item Finding from the Enum

using System;
using System.Linq;

namespace Ch06
{
class Program
{
public enum Planets
{
Sun = 0, /* Otherwise compiler will assign default value */
Mercury, /* compiler will assign 1 */
Venus, /* compiler will assign 2 */
Earth, /* compiler will assign 3 */
Mars, /* compiler will assign 4 */
Jupiter, /* compiler will assign 5 */
Saturn, /* compiler will assign 6 */
Uranus, /* compiler will assign 7 */
Neptune /* compiler will assign 8 */
}

static void Main(string[] args)
{
string enumItemToFind = "Jupiter";
Console.WriteLine(
"Is {0}, has been defined in the Planets enum? {1}",
enumItemToFind,
Enum.IsDefined(typeof(Planets), enumItemToFind));
}
}
}

This program will produce the following output:

Is Jupiter, has been defined in the Planets enum? True

Let’s find out how this IsDefined method works in C#.

The IsDefined method calls the IsEnumDefined method of the RuntimeType class:

public static bool IsDefined(Type enumType, object value)
{
return enumType.IsEnumDefined(value);
}

The IsDefined method then calls the InternalGetNames from the Enum class. InternalGetNames method will return all the names defined in the given enum and, using the IndexOf method from the Array class, CLR finds out whether the given item (input to the IsDefined method) has been defined in the specified enum. The implementation of the IsEnumDefined method is demonstrated below:

public override bool IsEnumDefined(object value)
{
/* Code removed*/
return (Array.IndexOf<object>
(Enum.InternalGetNames(this), /* Return all the names in the Enum */
value) >= 0);
/* IndexOf will find value(name) from the names of the enum */
/* Code removed*/
}

If you want to find the item based on the value instead of the name, for example:

Enum.IsDefined(typeof(Planets), Planets.Neptune) /* Planets.Neptune refers to 9 */

CLR will find the item based on the following code:

public override bool IsEnumDefined(object value)
{
/* Code removed */
ulong[] values = Enum.InternalGetValues(this);
ulong num = Enum.ToUInt64(value);
return (Array.BinarySearch<ulong>(values, num) >= 0);
}

Parsing

Listing 6-8 presents an example to help us understanding how CLR handles enum parsing.

Listing 6-8. Parsing an Item into an Enum

using System;
using System.Linq;

namespace Ch06
{
class Program
{
public enum Planets
{
Sun = 0, /* Otherwise compiler will assign default value */
Mercury, /* compiler will assign 1 */
Venus, /* compiler will assign 2 */
Earth, /* compiler will assign 3 */
Mars, /* compiler will assign 4 */
Jupiter, /* compiler will assign 5 */
Saturn, /* compiler will assign 6 */
Uranus, /* compiler will assign 7 */
Neptune /* compiler will assign 8 */
}

static void Main(string[] args)
{
string enumItemToFind = "Jupiter";
Planets result;
bool isParsable = Enum.TryParse<Planets>(enumItemToFind, true, out result);
}
}
}

The code in Listing 6-8 will try to find out whether enumItemToFind is parsable into the Planets Enum and return the parsed Enum into the result. If the parse operation is successful, CLR will return the appropriate Enum item into the result, otherwise it will return the default item (value with 0 or the item with lowest value) from the Enum as a result. In Listing 6-8, the result will hold Jupiter, otherwise it will be Sun, as Sun is the default of the Planets Enum. Let’s find out how this works.

To do the parsing operation, the CLR will call the TryParse method of the Enum class. This method will initialize an instance of the EnumResult struct, as shown in Figure 6-5. Note that you can get this view when you check the Show Member Types option in ildasm.exe tool.

images

Figure 6-5. EnumResult Struct in the System.Enum

In the TryParse method, CLR will call the TryParseEnum method with the instance of the EnumResult struct. The CLR will parse the given item into Enum, wrap the result into the EnumResult object, and pass back the instance of the EnumResult to the TryParse method. The EnumResult object will hold the relevant Enum in it and return the Boolean to the caller, as demonstrated in the following implementation of the TryParse method:

public static bool TryParse<TEnum>(string value, bool ignoreCase, out TEnum result)
where TEnum : struct
{
bool flag;
result = default(TEnum);
EnumResult parseResult = new EnumResult();
parseResult.Init(false);
if (flag = TryParseEnum(typeof(TEnum), value, ignoreCase, ref parseResult))
{
result = (TEnum)parseResult.parsedEnum;
}
return flag;
}

Summary

In this chapter, we have learned that enumerated types are strongly typed. For example, if a method requires a value of Planets.Neptune and you send Universe enumerated type, the C# compiler throws a compilation error. Enumerated types make the program easy to write, read, and maintain instead of using hard-coded value in the program. We also learned how the C# compiler takes care of the enum, how you can get values and names from an enum, and how the CLR parses an enum. In the next chapter, you will explore the delegate in C#.