Enumerators Decorated with the Flag Attribute
An enumerator decorated with the [Flags] or [FlagAttribute] attribute (they are one in the same), is able to store a byte that represents the concatenation of all possible combinations of the enumeration. Because of this concatenation it is required that the enumeration be numbered implementing a base 2 sequence (i.e. 0, 1, 2, 4). As seen in the sample code this evaluates to the following base 2 numbers:
0 = 0000
1 = 0001
2 = 0010
4 = 0100
For the sake of simplicity we added another enum to represent when all of the flags have been selected All = 7
7 = 0111
The following code (a simple aspx page and its code-behind) will display a the uses of Flags as well as the differences from regular enums. The code uses Bitwise OR to concatenate a flag (i.e. will toggle the bit on if either or both bits in the sequence are true (1)), as follows:
0010
0001
0011
The code also uses the the Bitwise Complement (~) operator as well as the Bitwise AND (&) operator to negate (remove) a specific flag (i.e. will toggle the bit off if both bits in the sequence are true (1)), as follows:
0111
~0010
0101
ASPX Code:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UsingEnums.aspx.cs" Inherits="UsingEnums" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Using Enumerators Page</title>
</head>
<body>
<form id="form1" runat="server">
<div style="text-align: center; width: 604px;">
<asp:Label ID="lblProjectRequirements" runat="server" Text="Project Requirements (Pick up to two)"></asp:Label>
<asp:CheckBox ID="chkAutoCorrect" runat="server" Text="Auto-correct" />
<asp:CheckBoxList ID="cblProjectPreference" runat="server">
</asp:CheckBoxList>
<asp:Button ID="btnSelectRequirements" runat="server" Text="Select Requirements"
OnClick="btnSelectRequirements_Click" Style="height: 26px" /><br />
<asp:Label ID="lblResults" runat="server" Height="91px" Width="552px"></asp:Label><br />
<asp:Button ID="btnCompare" runat="server" Text="Comapre Flags to Enums" OnClick="btnCompare_Click"
Style="height: 26px" /><br />
<asp:Label ID="lblFlagsDisplay" runat="server" Height="40px" Width="300px"></asp:Label>
<asp:Label ID="lblEnumDisplay" runat="server" Height="40px" Width="300px"></asp:Label>
</div>
</form>
</body>
</html>
Code-Behind:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
[Flags]
public enum ProjectPreference
{
/// <summary>
/// No project requirements are selected
/// </summary>
None = 0, // (0000)
/// <summary>
/// The project must be done as inexpensively as possible
/// </summary>
Cheap = 1, // (0001)
/// <summary>
/// The project must be done as quickly as possible
/// </summary>
Fast = 2, // (0010)
/// <summary>
/// The project must be of the highest quality
/// </summary>
Good = 4, // (0100)
/// <summary>
/// This is an illegal value, since only two can be slected at one time
/// </summary>
All = Cheap | Fast | Good // (0111)
}
// Based on Microsoft http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx
// Define an Enum without FlagsAttribute.
public enum SingleHue : short
{
Black = 0,
Red = 1,
Yellow = 2,
Blue = 4
}
// Define an Enum with FlagsAttribute.
[FlagsAttribute]
public enum MultiHue : short
{
Black = 0,
Red = 1,
Yellow = 2,
Blue = 4
}
public partial class UsingEnums : Page
{
private const int SKIP_FOR_NONE_AND_ALL = 2;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Get a string array of all the enum names
string[] projectTypes = Enum.GetNames(typeof(ProjectPreference));
// Don't display None or All enum values
string[] displayProjectPreferences = new string[projectTypes.Length - SKIP_FOR_NONE_AND_ALL];
int i = 0;
foreach (string projectType in projectTypes)
{
if (
!projectType.Equals(ProjectPreference.None.ToString())
&&
!projectType.Equals(ProjectPreference.All.ToString())
)
{
displayProjectPreferences[i++] = projectType;
}
}
cblProjectPreference.DataSource = displayProjectPreferences;
cblProjectPreference.DataBind();
}
}
protected void btnSelectRequirements_Click(object sender, EventArgs e)
{
ProjectPreference selectedProjectPreferences = new ProjectPreference();
try
{
// Gather all of the selected flags
foreach (ListItem liProjectPreference in cblProjectPreference.Items)
{
if (liProjectPreference.Selected)
{
// Concatenate the flag using the Enum.Parse method and the Bitwise OR (|) operator
selectedProjectPreferences |=
(ProjectPreference) Enum.Parse(typeof (ProjectPreference), liProjectPreference.Value, true);
}
}
// if all have been selected then ...
if (selectedProjectPreferences == ProjectPreference.All)
{
// if auto-correct is on remove the 'Cheap' flag
if (chkAutoCorrect.Checked)
{
// Use the Bitwise Complement (~) operator as well as the Bitwise AND (&)
// operator to negate (remove) a specific flag
selectedProjectPreferences &= ~ProjectPreference.Cheap;
foreach (ListItem liProjectPreference in cblProjectPreference.Items)
{
if (liProjectPreference.Value == ProjectPreference.Cheap.ToString())
{
liProjectPreference.Selected = false;
}
}
}
// otherwise throw an error
else
{
cblProjectPreference.SelectedIndex = -1;
throw new Exception("You can select no more than two project preferences.");
}
}
lblResults.Text = selectedProjectPreferences.ToString();
}
catch (Exception ex)
{
// The flag enumeration evaluates to 'All' - no coding required
string errMessage = string.Format("The error value of the flag enumeration is: {0}<br />",
selectedProjectPreferences);
// Reset to 'None'
selectedProjectPreferences = ProjectPreference.None;
errMessage += string.Format("{0}<br />The current value of the flag enumeration has been reset to: {1}",
ex.Message, selectedProjectPreferences);
lblResults.Text = errMessage;
}
}
protected void btnCompare_Click(object sender, EventArgs e)
{
// Display all possible combinations of values.
// Also display an invalid value.
string flagDisplay = "<b>With</b> FlagsAttribute:<br />";
for (int val = 0; val <= 8; val++)
{
flagDisplay += string.Format("{0,3} - {1}<br />", val, ((MultiHue)val));
}
lblFlagsDisplay.Text = flagDisplay;
string enumDisplay = "<b>Without</b> FlagsAttribute:<br />";
for (int val = 0; val <= 8; val++)
{
enumDisplay += string.Format("{0,3} - {1}<br />", val, ((SingleHue)val));
}
lblEnumDisplay.Text = enumDisplay;
}
}
Using the [Flags] attribute allows you to let the code handle some of the display of the flags, as the ToString() method will return a comma seperated list of enums (e.g. "Cheap, Fast"), it will also resolve the ToString() to an aggregated value (in this case to 'All' if all of the flags are selected) and finally in walking through code you will be able to hover and see the concatenated list of flags instead of a somewhat hard-to-decipher number. That is, if you are walking through code after a breakpoint you can hover over a [Flags] enum and see "Cheap | Good" instead of "5", making debugging far easier.
That wraps it up for this time. Hope it helps.