[ Team LiB ] |
Recipe 4.5 Using Enumerated Members in a BitmaskProblemAn enumeration of values is needed to act as bit flags that can be ORed together to create a combination of values (flags) in the enumeration. SolutionMark the enumeration with the Flags attribute: [Flags] enum Language { CSharp = 0x0001, VBNET = 0x0002, VB6 = 0x0004, Cpp = 0x0008 } Combining elements of this enumeration is a simple matter of using the bitwise OR operator (|). For example: Language lang = Language.CSharp | Language.VBNET; DiscussionAdding the Flags attribute to an enumeration marks this enumeration as individual bit flags that can potentially be ORed together. Using an enumeration of flags is no different than using a regular enumeration type. It should be noted that failing to mark an enumeration with the Flags would not generate an exception or a compile-time error even if the enumeration were used as bit flags. The addition of the Flags attribute provides you with three benefits. First, if the Flags attribute is placed on an enumeration, the ToString and ToString("G") methods return a string consisting of the name of the constant(s) separated by commas. Otherwise, these two methods return the numeric representation of the enumeration value. Note that the ToString("F") method returns a string consisting of the name of the constant(s) separated by commas, regardless of whether this enumeration is marked with the Flags attribute. For an indication of why this works in this manner, see the "F" formatting type in Table 4-1 in Recipe 4.1. The second benefit is that when you are using reflection to traverse code and encounter an enumeration, you are able to determine the developer's intention for this enumeration. If the developer explicitly defined this enumeration as containing bit flags (with the Flags attribute), you can use it as such. The third benefit is similar to the second benefit. If a developer marks an enumeration with the Flags attribute, you know that the developer intended this enumeration to contain only bit flags. This is similar to documenting one's code. Knowing that this enumeration can be used as a bitmask, you can more easily determine what is going on within this developer's code. The flags enumeration can be viewed as a single value or as one or more values combined into a single enumeration value. If you need to accept multiple languages at a single time, you could write the following code: Language lang = Language.CSharp | Language.VBNET; The variable lang is now equal to the bit values of the two enumeration values ORed together. These values ORed together will equal three, as shown here: Language.CSharp 0001
Language.VBNET 0010
ORed bit values 0011
The enumeration values were converted to binary and ORed together to get the binary value 0011 or 3 in base10. The compiler views this value both as two individual enumeration values (Language.CSharp and Language.VBNET) ORed together or as a single value (3). To determine whether a single flag has been turned on in an enumeration variable, we need to use the bitwise AND (&) operator, as follows: Language lang = Language.CSharp | Language.VBNET; if((lang & Language.CSharp) == Language.CSharp) Console.WriteLine("The enum contains the C# enumeration value"); else Console.WriteLine("The enum does NOT contain the C# value"); This code will display the text "The enum contains the C# enumeration value." The ANDing of these two values will produce either zero, if the variable lang does not contain the value Language.CSharp; or the value Language.CSharp, if lang contains this enumeration value. Basically, ANDing these two values looks like this in binary: Language.CSharp | Language.VBNET 0011
Language.CSharp 0001
ANDed bit values 0001
This case is dealt with in more detail in Recipe 4.6. In some cases, the enumeration can grow quite large. We could add many other languages to this enumeration, as shown here: [Flags] enum Language { CSharp = 0x0001, VBNET = 0x0002, VB6 = 0x0004, Cpp = 0x0008, FortranNET = 0x0010, JSharp = 0x0020, MSIL = 0x0080 } In the cases where a Language enumeration value is needed to represent all languages, we would have to OR together each value of this enumeration: Language lang = CSharp | VBNET | VB6 | Cpp | FortranNET | Jsharp | MSIL Instead of doing this, we can simply add a new value to this enumeration that includes all languages as follows: [Flags] enum Language { CSharp = 0x0001, VBNET = 0x0002, VB6 = 0x0004, Cpp = 0x0008, FortranNET = 0x0010, JSharp = 0x0020, MSIL = 0x0080, All = 0xFFFF } or: [Flags] enum Language { CSharp = 0x0001, VBNET = 0x0002, VB6 = 0x0004, Cpp = 0x0008, FortranNET = 0x0010, JSharp = 0x0020, MSIL = 0x0080, All = (CSharp | VBNET | VB6 | Cpp | FortranNET | JSharp | MSIL) } Now there is a single enumeration value, All, that encompasses every value of this enumeration. Notice that there are two methods of creating the All enumeration value. The second method is much easier to read; however, if individual language elements of the enumeration are added or deleted, you will have to modify the All value accordingly. Similarly, we could also add values to capture specific subsets of enumeration values as follows: [Flags] enum Language { CSharp = 0x0001, VBNET = 0x0002, VB6 = 0x0004, Cpp = 0x0008, FortranNET = 0x0010, JSharp = 0x0020, MSIL = 0x0080, All = 0x00FF, VBOnly = 0x0006, NonVB = 0x00F9 } or: [Flags] enum Language { CSharp = 0x0001, VBNET = 0x0002, VB6 = 0x0004, Cpp = 0x0008, FortranNET = 0x0010, JSharp = 0x0020, MSIL = 0x0080, All = (CSharp | VBNET | VB6 | Cpp | FortranNET | Jsharp | MSIL) VBOnly = (VBNET | VB6), NonVB = (CSharp | Cpp | FortranNET | Jsharp | MSIL) } Now we have two extra members in the enumerations: one that encompasses VB only languages (Languages.VBNET and Languages.VB6) and one that encompasses non-VB languages. |
[ Team LiB ] |