Enum
Think of a program that stores representations of flowers. A flower may have a purple color. A C# enum
could represent colors—this is more robust than a string
.
In a C# program, we must specify an enum
in a way similar to a class
. And then we can reuse it wherever needed. Enums are like ints with added compiler checks.
Enum
exampleHere is an enum
that expresses importance. An enum
type internally contains an enumerator list. The values (like Trivial and Critical) are ints like 1 and 4.
enum
variable and assign it to Importance.Critical
. This is like an int
, but with special constraints.enum
value with an if
-statement. The value is Critical, so "True" is printed.using System; class Program { enum Importance { None, Trivial, Regular, Important, Critical } static void Main() { // Step 1: create an enum local. Importance value = Importance.Critical; // Step 2: test against a known Importance value. if (value == Importance.Critical) { Console.WriteLine(true); } } }True
Parse
Sometimes we have a string
value that we want to convert to an equivalent enum
. We invoke the Enum.Parse
generic method to perform this conversion.
using System; class Program { enum PetType { None, Cat, Dog } static void Main() { string test = "Dog"; // Get enum from string. PetType result = Enum.Parse<PetType>(test); Console.WriteLine(result); } }Dog
We examine what enums look like in the Visual Studio debugger. We see that enums are each a separate type. The debugger shows that tagValue
is of type Program.TagType
.
enum
type is separate, we cannot become confused and try to change a VehicleType
into a PetType
.using System; class Program { enum TagType { None, BoldTag, ItalicsTag, HyperlinkTag } static void Main() { // Specify a tag instance. TagType tagValue = TagType.BoldTag; if (tagValue == TagType.BoldTag) { // Will be printed. Console.WriteLine("Bold"); } if (tagValue == TagType.HyperlinkTag) { // This is not printed. Console.WriteLine("Not true"); } } }Bold
Enum
advantagesWith an enum
, magic constants are separate. This modular design makes things easier to understand. Fewer bugs will be introduced.
enum
to ease maintenance.using System; enum CaseColor { Uncolored = 0, Red = 8001, Blue = 9001 } class Program { static void Main() { // This makes sense to read. Console.WriteLine("COLOR: {0}, {1}", CaseColor.Blue, (int)CaseColor.Blue); // This is more confusing to read. Console.WriteLine("COLOR: {0}, {1}", "Blue", 9001); } }COLOR: Blue, 9001 COLOR: Blue, 9001
We convert enums to strings for display on the Console
. Enum
values always have a name, such as TagType.None
(in the above example).
enum
values, you can call ToString
on the enum
variable in a program.Console.WriteLine
can automatically call the ToString
method.class Program { enum Visibility { None, Hidden = 2, Visible = 4 } enum AnimalType { None, Cat = 1, Dog = 2 } static void Main() { // ... Two enum variables. AnimalType animal = AnimalType.Dog; Visibility visible = Visibility.Hidden; // ... Use Console.WriteLine to print out heir values. System.Console.WriteLine(animal); System.Console.WriteLine(visible); } }Dog Hidden
We often use if
-statements with enums. But switch
is another option. And switch
is sometimes compiled to more efficient IL.
IsFormat()
works as a filter that tells us something about sets of enum
values—it contains a switch
statement.using System; class Program { enum FormatType { None, BoldFormat, // Is a format value. ItalicsFormat, // Is a format value. Hyperlink // Not a format value. } static void Main() { // ... Test enum with switch method. FormatType formatValue = FormatType.None; if (IsFormat(formatValue)) { // This is not reached, as None does not return a true value in IsFormat. Console.WriteLine("Error"); } // ... Test another enum with switch. formatValue = FormatType.ItalicsFormat; if (IsFormat(formatValue)) { // This is printed, as we receive true from IsFormat. Console.WriteLine("True"); } } /// <summary> /// Returns true if the FormatType is Bold or Italics. /// </summary> static bool IsFormat(FormatType value) { switch (value) { case FormatType.BoldFormat: case FormatType.ItalicsFormat: { // These 2 values are format values. return true; } default: { // The argument is not a format value. return false; } } } }True
Values are always initialized to zero when they are fields of a class
. Upon class
creation, an enum
field will also be initialized to zero (and the equivalent value).
enum
block (but this is not required or helpful usually).using System; enum CatFurColor { None, Orange, Grey }; class Program { static CatFurColor _color; static void Main() { // The enum is a field, so it has its default value of None. Console.WriteLine("DEFAULT ENUM VALUE: {0}", _color); } }DEFAULT ENUM VALUE: None
Here we apply the Stack
collection in .NET. With Stack
, we can develop a parser that keeps the most recently encountered enum
value on the top.
Stack
can only have TagType
values added to it. This is an example of type checking and validation.TagType.ItalicsTag
.using System.Collections.Generic; class Program { enum TagType { None, // integer value = 0 BoldTag, // 1 ItalicsTag, // 2 HyperlinkTag, // 3 } static void Main() { // Create a Stack of enums. var stack = new Stack<TagType>(); // ... Add enum values to our Stack. stack.Push(TagType.BoldTag); // Add bold. stack.Push(TagType.ItalicsTag); // Add italics. // ... Get the top enum value. TagType thisTag = stack.Pop(); // Get top tag. System.Console.WriteLine("POP RESULT: " + thisTag); // Peek at the top. var peeked = stack.Peek(); System.Console.WriteLine("PEEK RESULT: " + peeked); } }POP RESULT: ItalicsTag PEEK RESULT: BoldTag
An enum
has an underlying type. Each time we use the enum
, we are using the underlying type. The enum
has syntactic sugar on top.
int
type, but we can adjust this to a different numeric type.enum
with a type of byte
. This is sometimes useful on small enums. A byte
can only contain 256 different values.CoffeeSize
enum
will use memory equivalent to a byte
. This can make classes more efficient and smaller.using System; class Program { enum CoffeeSize : byte { None, Tall, Venti, Grande }; static void Main() { // ... Create a coffee size local. CoffeeSize size = CoffeeSize.Venti; Console.WriteLine(size); } }Venti
GetUnderlyingType
We can determine an enum
's type (like int
) at runtime. Enum.GetUnderlyingType
, a static
method, determines the underlying type.
enum
Importance. For this example it uses an underlying type of byte
.GetUnderlyingType
method is called, the System.Byte
type is returned.using System; class Program { enum Importance : byte { Low, Medium, High }; static void Main() { // Determine the underlying type of the enum. Type type = Enum.GetUnderlyingType(typeof(Importance)); Console.WriteLine(type); } }System.Byte
An enum
value cannot be null
. It is a value type like an int
. To avoid the "cannot convert null
" error, use a special None
constant as the first enum
item.
enum Color { None, Blue, Red } class Program { static void Main() { Color c = null; } }Error CS0037 Cannot convert null to 'Color' because it is a non-nullable value typeenum Color { None, Blue, Red } class Program { static void Main() { Color c = Color.None; } }
Flags
The C# language allows us to specify a Flags
attribute on an enum
. This enables the enum
to be used as a bit field. We can use combinations of enum
values this way.
enum
called DataInfo
that has 4 different flag values. We set 2 of them at once in Main()
.if
-statement, we find that OptionA
and OptionC
are set to true, but OptionB
is not set. This is the correct result.using System; class Program { [Flags] enum DataInfo { None = 0, OptionA = 1, OptionB = 2, OptionC = 4, } static void Main() { var info = DataInfo.OptionA | DataInfo.OptionC; // See if current flag is set. if ((info & DataInfo.OptionA) == DataInfo.OptionA && (info & DataInfo.OptionB) != DataInfo.OptionB && // Not OptionB. (info & DataInfo.OptionC) == DataInfo.OptionC) { Console.WriteLine("Has OptionA, OptionC, but not OptionB."); } } }Has OptionA, OptionC, but not OptionB.
enum
Enums are fast. They are almost never a performance concern. They are just syntactic sugar on a type like int
, which is also fast.
enum
value. We run it in a tight loop for many iterations.int
. By comparing these versions of the code, we can see any possible performance impact from enums.enum
test takes the same amount of time as the int
test—the enum
carries no performance penalty over int
.using System; using System.Diagnostics; class Program { enum TestType { None, Valid, Invalid } const int _max = 10000000; static void Main() { // Version 1: use enum in if-statement. var s1 = Stopwatch.StartNew(); var temp1 = TestType.Valid; for (int i = 0; i < _max; i++) { if (temp1 == TestType.Invalid) { return; } } s1.Stop(); // Version 2: use int in if-statement. var s2 = Stopwatch.StartNew(); var temp2 = 0; for (int i = 0; i < _max; i++) { if (temp2 == 2) { return; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } }0.27 ns if enum 0.27 ns if int
enum
We can determine the exact performance of enums by examining the IL (intermediate language) of a compiled C# program. Enums are loaded with ldc.i4
.
enum
of just a constant 4-byte integer, exactly like any other const
int
.enum
, 3 const
ints, and 3 static
readonly
ints. The IL shows how the values are loaded onto the evaluation stack.const
ints are loaded with ld.c
, so they perform the same. But static
ints are loaded with ldsfld, which is slower.using System; enum Test { Cat, Dog, Rabbit } class Program { const int _cat = 0; const int _dog = 1; const int _rabbit = 2; static readonly int _cat2 = 0; static readonly int _dog2 = 1; static readonly int _rabbit2 = 2; static void Main() { Method(Test.Cat); Method(Test.Dog); Method(Test.Rabbit); Method(_cat); Method(_dog); Method(_rabbit); Method(_cat2); Method(_dog2); Method(_rabbit2); } static void Method(Test test) { } static void Method(int value) { } }.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 1 L_0000: ldc.i4.0 L_0001: call void Program::Method(valuetype Test) L_0006: ldc.i4.1 L_0007: call void Program::Method(valuetype Test) L_000c: ldc.i4.2 L_000d: call void Program::Method(valuetype Test) L_0012: ldc.i4.0 L_0013: call void Program::Method(int32) L_0018: ldc.i4.1 L_0019: call void Program::Method(int32) L_001e: ldc.i4.2 L_001f: call void Program::Method(int32) L_0024: ldsfld int32 Program::_cat2 L_0029: call void Program::Method(int32) L_002e: ldsfld int32 Program::_dog2 L_0033: call void Program::Method(int32) L_0038: ldsfld int32 Program::_rabbit2 L_003d: call void Program::Method(int32) L_0042: ret }
GetName
, GetnamesBuilt-in methods get strings that represent enums. With GetName
, we can get the name for an enum
value. With GetNames
we get all the string
representations at once.
It is possible to format the values stored in enums in different ways. We can display an integer representation, or a hex representation.
Enums are values. We can use enums to index arrays. This approach is useful for some kinds of tables or data structures in programs.
Enums enhance clarity and reduce the probability of invalid constants. We use them to represent constant values (such as integers) in a consistent way.