Tuesday, January 15, 2008

 

Enums - The Basics and Beyond

All C# programmers are familiar with the enum keyword. Conceptually speaking, an enum is not much more than a named list of constants that represent possible numeric values.


enum MyEnum1
{
ValueA,
ValueB
}
 

As stated in a previous post, it is possible for the last item in the enum declaration to end with a comma. This is intended to facilitate easier resposition of the values, as well as easier auto-generation of enums from development tools.


enum MyEnum2
{
ValueA,
ValueB,
}
 

When an enum is declared, the compiler automatically creates a new type derived from the System.Enum class. That new type uses one of the numeric system types to store the enum value. By default, an int is used to as the underlying type to store the value. But, that underlying type can actually be a signed or unsigned byte, short, int or long.

NOTE: If the underlying data type is changed, the enum may no longer be CLS compliant because some languages such as VB.NET do not support all of the types.


enum MyEnum3a // Underlying data type is int
{
ValueA,
ValueB
}

enum MyEnum3b : ulong // Underlying data type is ulong
{
ValueA,
ValueB
}
 

Each value listed in the enum is associated with a number in the underlying numeric data type. By default, the first value in the enum is assigned the numeric value of 0. But, the initial value can be changed. The underlying numeric value is automatically incremented by one for each enum value thereafter for which a specific value is not specified.


enum MyEnum4a
{
ValueA, // Underlying numeric value is 0
ValueB, // Underlying numeric value is 1
ValueC, // Underlying numeric value is 2
ValueD // Underlying numeric value is 3
}

enum MyEnum4b
{
ValueA, // Underlying numeric value is 0
ValueB, // Underlying numeric value is 1
ValueC = 200, // Underlying numeric value is 200
ValueD // Underlying numeric value is 201
}

enum MyEnum4c
{
ValueA = 100, // Underlying numeric value is 100
ValueB, // Underlying numeric value is 101
ValueC = 200, // Underlying numeric value is 200
ValueD // Underkying numeric value is 201
}
 

Because each value of an enum is associated with an underlying numeric data type, you can actually use enum values in math and conditional operations as long as you cast it to the underlying type.


int x = 2 * (int)MyEnum4c.ValueC; // x == 400

int y = 500;
if (y > (int)MyEnum4c.ValueD)
{
DoSomething(y);
}
 

You can also use enums in conditionals without casting as long as you are comparing it against enum values of the same enum type.



MyEnum4c e = MyEnum4c.ValueC;

if (e < MyEnum4c.ValueC)
{
...
}
else if (e == MyEnum4c.ValueC)
{
...
}
else
{
...
}

switch (e)
{
case MyEnum4c.ValueA:
case MyEnum4c.ValueB:
DoSomething();
break;

case MyEnum4c.ValueC:
DoSomething();
break;

case MyEnum4c.ValueD:
DoSomething();
break;

// Always a good idead to trap default in case someone adds
// another enum value but forgets to add it to the case.
default:
Debug.Assert(false, "Oops! Unknown enum value " + e.ToString() );
}

 

It is convention to declare an enum value None if you will need to "clear" the value, or represent an initial or unknow state. The enum values Unknown and Default are also used frequently in practice. These are typically declared as the first value of the enum, and therefore default to an assigned value of zero.


enum MyEnum5a
{
None,
ValueA,
ValueB
}

enum MyEnum5b
{
Unknown,
ValueA,
ValueB
}

enum MyEnum5c
{
Default,
ValueA,
ValueB
}
 

Although it may seem tempting to do so, it is considered to be bad practice to "mark" the boundaries of an enum with another enum value; such "markers" do not represent data values, and have no business being declared in something for which the sole purpose of being created is to represent possible data values.


// *** Start Bad Practice **
enum MyEnum6a
{
ValueA,
ValueB,
LastValue
}

enum MyEnum6b
{
ValueA,
ValueB,
LastValue = ValueB,
}
// *** End Bad Practice ***
 

The reason someone may typically want to "mark" the last enum value is often because he or she wants to loop over all possible values. However, marking the boundaries is error-prone for several reasons:



Instead, you should consider using the static Enum.GetValues(), Enum.GetName() and Enum.GetNames() methods.


foreach(int x in Enum.GetValues(typeof(MyEnum4c)))
{
DoSomething(x);
}
 

Under most circumstances, a variable of type enum can only contain a single value. The [Flags] attribute (System.FlagsAttribute) can be used, however, to indicate that a variable of type enum can be a composite of zero or more enum values.


[Flags]
enum MyEnum7
{
ValueA = 1,
ValueB = 2,
ValueC = 4,
ValueD = 8
}
 

As a general rule, when using the [Flags] attribute, you will want to specifically map each unique enum value to an underlying numeric value that is a power of 2 (see example above). The compiler will not automatically do that for you, and will not enforce that you do it yourself either. This is because you may want to define "group" values that represent combinations of two or more possible values.


[Flags]
enum MyBits : byte
{
None = 0,
Bit0 = 1,
Bit1 = 2,
Bit2 = 4,
Bit3 = 8,
Bit4 = 16,
Bit5 = 32,
Bit6 = 64,
Bit7 = 128,
LowerNibble = Bit0 | Bit1 | Bit2 | Bit3, // Group value
UpperNibble = Bit4 | Bit4 | Bit6 | Bit7 // Group value
}
 

As was implied in the example above, the bitwise operators and bitwise assignment operators are very useful for enums that have been declared with the [Flags] attribute.




// Assign multiple enum values
MyBits bitsA = MyBits.Bit0 | MyBits.Bit3 | MyBits.Bit7;
MyBits bitsB = MyBits.Bit0 | MyBits.Bit1;

// Get all the enum values that are not on in bitsA
MyBits oppositeBits = ~bitsA; // See text below this example!

// Get all the enum values that are on in bitsA and/or bitsB
MyBits compositeBits = bitsA | bitsB;

// Get only those enum values that are on in both bitsA and bitsB
MyBits commonBits = bitsA & bitsB;

// Get only those enum values that are unique to bitsA and bitsB
MyBits uniqueBits = bitsA ^ bitsB;

// Turn on bit 4, then turn it back off
bitsA |= MyBits.Bit4;
bitsA &= (~MyBits.Bit4); // See text below this example!

// Determine if a particular bit is on
bool bit3On = ((bitsA & MyBits.Bit3) == MyBits.Bit3);

// Get only the values for bits 0 - 3
MyBits maskLowerNibble = (bitsA & MyBits.LowerNibble);

// Deteremine if there are any high bits
bool hasHighBits = ((bitsA & MyBits.UpperNibble) > MyBits.None);

// You get the idea!
 

When it comes to the underlying numeric value of an enum, there is something to be aware of. If you are not careful, it is possible to create an enum variable that does not contain a valid enum value (or values if using [Flags]). In fact, in the example above, the bitwise NOT (invert) was used in two places. Had MyBits not declared an enum value for every bit in the underlying byte numeric type, those operations would have created invalid values. Several other samples are shown below.


enum MyEnum8
{
ValueA, // Numeric Value == 0
ValueB // Numeric Value == 1
}

// Successfully assign invalid numeric values to an enum
MyEnum8 invalidValue1 = ~MyEnum8.ValueA; // invalidValue1 == -1
MyEnum8 invalidValue2 = (MyEnum8)22; // invalidValue2 == 22
 

Also, if you have declared an enum with the [Flags] attribute, you need to be cautious when using switch statements since the switch works on the underlying numeric value that may actually be a composite of one or more enum values.


switch (bitsA)
{
case MyBits.Bit0:
OnlyBitZeroIsOn();
break;

case MyBits.Bit1:
OnlyBitOneIsOn();
break;

case MyBits.Bit0 | MyBits.Bit1:
BothBitsZeroAndOneAreOn();
break;
}
 

If an enum does not have the [Flags] attribute, converting it back and forth to a string is fairly straight forward.


// Convert to a string
MyEnum8 myEnum8 = MyEnum8.ValueA;
string valueStr = bitsA.ToString();

// Convert back from a string (throw exception if it cannot)
valueStr = "ValueB";
myEnum8 = (MyEnum8)Enum.Parse(typeof(MyEnum8), valueStr);
 

If the enum has been declared with [Flags] attribute, however, conversion back and forth to a string is a bit more difficult. Perhaps I will make that the topic of my next post.

For more information, visit the following MSDN links.
System.Enum class
C# Language Reference

Comments:
That was usefull.

and you can find more information here too

http://www.csharp-station.com/Tutorials/Lesson17.aspx

C# Programmer
 
If you have integer value (say 25) how do you convert it to array representating elements of Enum which was not defineed with [FlagsAttribute], so default elements are still having 0,1,2,3 values?
 
Sorry it took so long (I didn't see your question). You could do something like below.

enum MyEnum : uint
{
b0,
b1,
b2,
b3,
b4,
b5,
b6,
b7,
}

private MyEnum[] ConvertToEnumArray(uint someNumber)
{
uint powerOfTwo;
List<MyEnum> returnList = new List<MyEnum>();
foreach(MyEnum enumValue in Enum.GetValues(typeof(MyEnum)))
{
powerOfTwo = (uint)Math.Pow(2.0, (double)(uint)enumValue);
if ( (someNumber & powerOfTwo) > 0)
{
returnList.Add(enumValue);
}
else if (powerOfTwo > someNumber)
{
break;
}
}

MyEnum[] returnArray = new MyEnum[returnList.Count];
returnList.CopyTo(returnArray);
return returnArray;
}
 
Ok, I know that previous comment from me was a bit of overkill, but I was really trying to show using Enum.GetValues() and casting to and from enums. There is a much simpler and faster executing way if you want to blindly assume the enum values exist. The foreach loop in the previous comment can be replaced with a simple while loop with a bit shift and a bit counter.

int bit = 0;
while (someNumber > 0)
{
if ((someNumber & 1) == 1)
{
returnList.Add((MyEnum)bit);
}

someNumber >>= 1;
bit++;
}
 
Post a Comment





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]