Introduction
The Common Language Runtime and the Framework Class Library work together to make enumerated types and bit flags powerful object-oriented types that offer many unrecognized features to .NET developers. An enumeration is a special type that maps a set of names to numeric values. Using them is an alternative to embedding constants in your code, and provides a higher level of nominal type safety. Enumerated types look just like ordinary types in metadata, although they abide by a strict set of rules as defined on the Common Type Specification (CTS). An enum
type itself derives from System.Enum
, which itself derives from System.ValueType
. Each is backed by a specific primitive data type, one of Boolean
, Char
, Byte
, Int16
, Int32
, Int64
, SByte
, UInt16
, UInt32
, IntPtr
, Single
and Double
. Int32
is used as the default in most cases. However, unlike other value types, an enumerated type cannot define any methods, properties, or events. An instance of a single enum
contains a single field representing its value. And because enum
s are value types, having an instance of one is essentially the same as having a value of its backing store type, except that you can refer to it by type name. In C#, you simply write the following to create a new enum
:
enum Color : byte
{
Red,
Green,
Blue
}
The part specifying the enum
’s type is optional. The C# compiler handles the necessary translation into metadata, which follows the above rules:
.class private auto ansi sealed Color
extends [mscorlib]System.Enum
{
.field public specialname rtspecialname uint8 value__
.field public static literal valuetype Color Red = uint8(0x00)
.field public static literal valuetype Color Green = uint8(0x01)
.field public static literal valuetype Color Blue = uint8(0x02)
The Color enum
could then be used in a program that prompts a user to enter his favorite color. Use the enum
to present the list, parse the input, and then pass around the selection to various routines:
using System;
enum Color : byte
{
Red,
Green,
Blue
}
class ColorPicker
{
internal static void Main()
{
Color favorite = SelectFavoriteColor();
RespondToFavoriteColor(favorite);
}
static Color SelectFavoriteColor()
{
Color favorite = (Color)(0xFF);
do
{
Console.Write("Please enter your favorite color (");
foreach (string name in Enum.GetNames(typeof(Color)))
Console.Write(" {0} ", name);
Console.Write("): ");
string input = Console.In.ReadLine();
try
{
favorite = (Color)Enum.Parse(typeof(Color), input, true);
}
catch (ArgumentException)
{
Console.WriteLine("Bad input, please choose again!");
Console.WriteLine();
}
}
while (favorite == (Color)(0xFF));
return favorite;
}
static void RespondToFavoriteColor(Color c)
{
switch (c)
{
case Color.Red:
Console.WriteLine("you like red");
break;
case Color.Green:
Console.WriteLine("you like green");
break;
case Color.Blue:
Console.WriteLine("you like blue");
break;
default:
Console.WriteLine("error!");
break;
}
}
}
Here is the output:
Please enter your favorite color ( Red Green Blue ): Black
Bad input, please choose again!
Please enter your favorite color ( Red Green Blue ): Green
you like green
Enumerated Types: A Closer Look
As we have seen above, the compiler translates the code to emit metadata that is zero based. Because an enum
is a special type that maps a name to an integer, an enumerated type is a type that defines a set of symbolic names and value pairs. So when we write:
internal enum Color {
White,
Red,
Green,
Blue,
Purple
}
Rather than writing code that uses 0 to represent white, 1 to represent red, .NET developers shouldn't hard-code numbers into their code; they should use an enumerated type. A look at the metadata reveals why. So basically, an enumerated type is just a structure with a bunch of constant fields defined in it and one instance field. The constant fields are emitted to the assembly’s metadata and can be accessed via reflection. This means that you can get all of the symbols and their values associated with an enumerated type at runtime. Here is a code example that uses the Enum
type’s static GetUnderlyingType
:
using System;
public enum Color {
White,
Red,
Green,
Blue,
Orange,
}
public static class Program {
public static void Main() {
Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));
Color c = Color.Blue;
Console.WriteLine(c);
Console.WriteLine(c.ToString());
Console.WriteLine(c.ToString("G"));
Console.WriteLine(c.ToString("D"));
Console.WriteLine(c.ToString("X"));
Console.WriteLine(Enum.Format(typeof(Color), 3, "G"));
Color[] colors = (Color[]) Enum.GetValues(typeof(Color));
Console.WriteLine("Number of symbols defined: " + colors.Length);
Console.WriteLine("Value\tSymbol\n-----\t------");
foreach (Color color in colors) {
Console.WriteLine("{0,5:D}\t{0:G}", color);
}
c = (Color) Enum.Parse(typeof(Color), "orange", true);
try {
c = (Color) Enum.Parse(typeof(Color), "Brown", false);
}
catch (ArgumentException) {
Console.WriteLine("Brown is not defined by the Color enumerated type.");
}
c = (Color) Enum.Parse(typeof(Color), "1", false);
c = (Color) Enum.Parse(typeof(Color), "23", false);
Console.WriteLine(Enum.IsDefined(typeof(Color), 1));
Console.WriteLine(Enum.IsDefined(typeof(Color), "White"));
Console.WriteLine(Enum.IsDefined(typeof(Color), "white"));
Console.WriteLine(Enum.IsDefined(typeof(Color), 10));
SetColor((Color) 3);
try {
SetColor((Color) 547);
}
catch (ArgumentOutOfRangeException e) {
Console.WriteLine(e);
}
}
public static void SetColor(Color c) {
if (!Enum.IsDefined(typeof(Color), c)) {
throw(new ArgumentOutOfRangeException("c", c, "You didn't pass a valid Color"));
}
}
}
The output is:
System.Int32
Blue
Blue
Blue
3
00000003
Blue
Number of symbols defined: 5
Value Symbol
-------- ------
0 White
1 Red
2 Green
3 Blue
4 Orange
Brown is not defined by the Color enumerated type.
True
True
False
False
System.ArgumentOutOfRangeException: You didn't pass a valid Color
Parameter name: c
Actual value was 547.
at Program.SetColor(Color c)
at Program.Main()
Flag-Style Enumerations
A flag is an algorithm that indicates the status of an operation. And sometimes, rather than having the values for an enum
exclusive, applications must represent a single instance as a combination of values. An enumerated type may be annotated with the System.FlagsAttribute
custom attribute. For example, consider a set of permissions for files. It’s common to open a file for both Read
and Write
simultaneously. Using a flag-style enum
enables you to represent this idea. Notice the numeric value for Read
and Write
are in the power of two, starting with 1. This is because combining or extracting values from a single instance is done using a bitwise AND or OR. Additional values would occupy 4, 8, 16, 32, and so on. Looking at the code below, 1 | 2 = 3 (that is, 1 bitwise Or 2 = 3):
using System;
[Flags]
enum FileAccess
{
Read = 1,
Write = 2,
ReadWrite = 3
}
class App {
public static void Main()
{
FileAccess rw1 = FileAccess.Read | FileAccess.Write;
Console.WriteLine("rw1 == {0}", rw1);
FileAccess rw2 = FileAccess.ReadWrite;
Console.WriteLine("rw2 == {0}", rw2);
Console.WriteLine("rw1 == rw2? {0}", rw1 == rw2);
if (rw1 == FileAccess.Read)
Console.WriteLine("try #1: read permitted");
else
Console.WriteLine("try #1: read denied");
if ((rw2 & FileAccess.Read) != 0)
Console.WriteLine("try #2: read permitted");
else
Console.WriteLine("try #2: read denied");
}
}
The output is:
rw1 == ReadWrite
rw2 == ReadWrite
rw1 == rw2? True
try #1: read denied
try #2: read permitted
The above example is an example of a program that works with bit flags. When defining an enumerated type that can be used to identify bit flags, you should explicitly assign a numeric value to each symbol. Documentation recommends that you apply the System.FlagsAttribute
custom attribute type to the enumerated type as shown below:
[Flags]
internal enum Actions {
None = 0
Read = 0x0001,
Write = 0x0002,
ReadWrite = Actions.Read | Actions.Write
Delete = 0x0004
Query = 0x0008
Sync = 0x0010
}
Because Actions
is an enumerated type, you can use all of its methods when working with bit flag enumerated types. For example, assume you had written the following code:
Actions actions = Actions.Read | Actions.Delete;
Console.WriteLine(actions.ToString());
When ToString
is called, it attempts to translate the numeric value into its symbolic equivalent, as ToString
derives from Object
and represents an object
as a string
. The numeric value is 0x0005, which has no numeric symbolic equivalent. However, the ToString
method detects the existence of the [Flags]
attribute on the Actions
type. ToString
now treats the numeric value not as a single value but as a set of flags. Because the 0x0001 and 0x0004 flags are set, ToString
generates the following string
: “Read
”, “Delete
”. If you remove the [Flags]
attribute from the Actions
type, ToString
would return “5
”. Note that the ToString
method offers three ways to format the output: “G
” (general), “D
” (decimal), and “X
” (hex).
A Brief Word on Formatting
When converting numbers into string
s using the ToString
method, a more extensive default formatting pattern is used:
Specifier Name Meaning
---------------------------------------------------------------------------------------
C Currency Formats a string into a culture-aware currency attributes
D Decimal Formats an integral into a decimal
E Scientific Uses scientific engineering notation
F Fixed Point Prints a fixed-point format
G General General numeric formatting
N Number General numeric formatting that uses culture information
P Percent Scales a number by 100 and formats it as a percentage
R Round Trip Formats a number such that parsing the result
will not result in loss of precision
X Hexadecimal Formats a number to its base-16 equivalent.
Briefly consider this code:
using System;
public sealed class Program {
public static void Main() {
Console.WriteLine("C: {0}", 39.22M.ToString("C"));
Console.WriteLine("D: {0}", 982L.ToString("D"));
Console.WriteLine("E: {0}", 3399283712.382387D.ToString("E"));
Console.WriteLine("F: {0}", .993F.ToString("F"));
Console.WriteLine("G: {0}", 32.559D.ToString("G"));
Console.WriteLine("N: {0}", 93823713.ToString("N"));
Console.WriteLine("P: {0}", .59837.ToString("P"));
Console.WriteLine("R: {0}", 99.33234D.ToString("R"));
Console.WriteLine("X: {0}", 369329.ToString("X"));
}
}
And notice the output using ToString
:
C: $39.22
D: 982
E: 3.399284E+009
F: 0.99
G: 32.559
N: 93,823,713.00
P: 59.84 %
R: 99.33234
X: 5A2B1
Back To Enumerated Types and Bit Flags
The FileAttributes
type is defined in the FCL as follows:
[Flags, Serializable]
public enum FileAttributes
{
Archive = 0x20,
Compressed = 0x800,
Device = 0x40,
Directory = 0x10,
Encrypted = 0x4000,
Hidden = 2,
Normal = 0x80,
NotContentIndexed = 0x2000,
Offline = 0x1000,
ReadOnly = 1,
ReparsePoint = 0x400,
SparseFile = 0x200,
System = 4,
Temporary = 0x100
}
To determine whether a file is hidden, you could execute code like this:
String file = @"C:\Pagefile.sys";
FileAttributes attributes = File.GetAttributes(file);
Console.WriteLine("Is {0} hidden? {1}", file,
(attributes & FileAttributes.Hidden) != 0);
Here is code demonstrating how to change a file’s attributes to read-only and hidden:
File.SetAttributes(@"C:\pagefile.sys", FileAttributes.ReadOnly | FileAttributes.Hidden);
Take care to note that while enumerated types and bit flags are similar, they do not have the semantics. Enum
s represent single numeric values, and bit flags represent a set of bits, some of which are on, some of which are off. Finally, here is an example of the use of enumerated types and bit flags using the Actions
type and Enum
’s static Parse
method:
using System;
using System.IO;
[Flags]
public enum Actions {
Read = 0x0001,
Write = 0x0002,
Delete = 0x0004,
Query = 0x0008,
Sync = 0x0010
}
public static class Program {
public static void Main() {
String file = @"C:\windows\system32\autoexec.NT";
FileAttributes attributes = File.GetAttributes(file);
Console.WriteLine("Is {0} hidden? {1}", file,
(attributes & FileAttributes.Hidden) != 0);
Actions actions = Actions.Read | Actions.Write;
Console.WriteLine(actions.ToString());
Actions a = (Actions) Enum.Parse(typeof(Actions), "Query", true);
a = (Actions) Enum.Parse(typeof(Actions), "Query, Read", false);
a = (Actions) Enum.Parse(typeof(Actions), "28", false);
Console.WriteLine(a.ToString());
a = (Actions) Enum.Parse(typeof(Actions), "Delete, Sync");
Console.WriteLine(a.ToString());
}
}
Here is the output:
Is C:\windows\system32\autoexec.NT hidden? False
Read, Write
Delete, Query, Sync
Delete, Sync
Suggested Reading
- Professional .NET Framework 2.0 by Joe Duffy
- CLR via C# by Jeffrey Richter
History
- 23rd July, 2009: Initial post
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.