Enums & Enum Flags In C# & .NET For Dummies
[C#, .NET]
Suppose you were operating a burger shop. Initially, you just sold the burger - bun and patty. Then, as time progressed and you made more money, you began to improve your burger.
Initially you only offered tomato sauce.
If we were to model the toppings, we could do it like this:
public class Burger
{
public bool TomatoSauce { get; set; }
}
Things progress even further and you offer something else:
public class Burger
{
public bool TomatoSauce { get; set; }
public bool Mayonnaise { get; set; }
}
You make still more improvements:
public class Burger
{
public bool TomatoSauce { get; set; }
public bool Mayonnaise { get; set; }
public bool BarbecueSauce { get; set; }
public bool Ketchup { get; set; }
}
At this juncture your class is starting to become unwieldy.
We could change it to use codes, like this:
public class Burger
{
public int Condiment { get; set; }
}
We then give each condiment a code.
public static class CondimentCodes
{
private const int TomatoSauce = 1;
private const int Mayonnaise = 2;
private const int BarbecueSauce = 3;
private const int Ketchup = 4;
}
This allows us to specify a condiment like this:
var burger = new Burger
{
Condiment = CondimentCodes.TomatoSauce
};
The problem becomes apparent when we output a burger to the console.
Console.WriteLine(burger.Condiment);
This will print the following:
1
We can solve this problem by using strings
instead of integer
codes.
public class Burger
{
public string Condiment { get; set; } = "";
}
public static class CondimentCodes
{
public const string TomatoSauce = nameof(TomatoSauce);
public const string Mayonnaise = nameof(Mayonnaise);
public const string BarbecueSauce = nameof(BarbecueSauce);
public const string Ketchup = nameof(Ketchup);
}
We then write a small program:
var burger = new Burger
{
Condiment = CondimentCodes.TomatoSauce
};
Console.WriteLine(burger.Condiment);
This prints the following:
TomatoSauce
Now, suppose a customer wants both Tomato Sauce and Mayonnaise?
This technique quickly breaks down.
In addition, it has the following problems:
-
Difficult to maintain
-
Difficult to read
-
No type safety. Nothing stops you from doing the following:
var burger = new Burger { Condiment = "They not like us!"; };
You can solve this problem using an enumeration, an Enum.
An enum is a set of named constants, backed by an underlying numeric type.
We define one as follows:
public enum Condiments
{
TomatoSauce,
Mayonnaise,
BarbequeSauce,
Ketchup
}
What happens here is that the compiler will generate numeric values for each specified value for you, starting at 0
.
In other words, behind the scenes this takes place:
public enum Condiments
{
TomatoSauce = 0,
Mayonnaise = 1,
BarbequeSauce = 2,
Ketchup = 3,
}
You don’t have to start from 0
. You can start from whatever value you want:
public enum Condiments
{
TomatoSauce = 1000,
Mayonnaise
BarbequeSauce
Ketchup
}
This is the same as:
public enum Condiments
{
TomatoSauce = 1000,
Mayonnaise = 1001,
BarbequeSauce = 1002,
Ketchup = 1003,
}
You can also specify the values in any order:
public enum Condiments
{
TomatoSauce = 4,
Mayonnaise = 3,
BarbequeSauce = 2,
Ketchup = 1,
}
And you can also skip values if you want:
public enum Condiments
{
TomatoSauce = 10,
Mayonnaise = 20,
BarbequeSauce = 30,
Ketchup = 40,
}
Having defined our enum, we then update our type as follows:
public class Burger
{
public Condiments Condiment { get; set; }
}
Our updated program will now look like this:
var burger = new Burger
{
Condiment = Condiments.TomatoSauce
};
Console.WriteLine(burger.Condiment);
Much easier to read.
It also has the additional benefit that it prints the following:
TomatoSauce
Returning to our problem - how do we specify more than one condiment?
We can use some boolean logic - bit flags to address this.
We can redefine our enum as follows (note the values - they are all powers of 2
)
public enum Condiments
{
TomatoSauce = 1,
Mayonnaise = 2,
BarbequeSauce = 4,
Ketchup = 8,
}
You don’t need to compute the values for yourself - you can achieve the same result using bit shifting.
public enum Condiments
{
TomatoSauce = 1 << 0,
Mayonnaise = 1 << 1,
BarbequeSauce = 1 << 2,
Ketchup = 1 << 3,
}
This technique is very convenient when the enums have many values.
To specify that a burger has BOTH tomato sauce and mayonnaise, we do the following:
var burger = new Burger
{
Condiment = Condiments.TomatoSauce | Condiments.Mayonnaise
};
Console.WriteLine(burger.Condiment);
If we print this, we get the following result:
3
Isn’t this the same problem we started with?
Not quite.
We can interrogate the enum
to determine what its constituents are, using the HasFlag method.
if (burger.Condiment.HasFlag(Condiment.TomatoSauce))
Console.WriteLine("This burger has tomato sauce");
else
Console.WriteLine("This burger does not have tomato sauce");
if (burger.Condiment.HasFlag(Condiment.Mayonnaise))
Console.WriteLine("This burger has mayonnaise");
else
Console.WriteLine("This burger does not have mayonnaise");
if (burger.Condiment.HasFlag(Condiment.BarbequeSauce))
Console.WriteLine("This burger has barbeque sauce");
else
Console.WriteLine("This burger does not have barbeque sauce");
if (burger.Condiment.HasFlag(Condiment.Ketchup))
Console.WriteLine("This burger has ketchup");
else
Console.WriteLine("This burger does not have ketchup");
This will print the following:
This burger has tomato sauce
This burger has mayonnaise
This burger does not have barbeque sauce
This burger does not have ketchup
We can also introduce some flexibility.
Ketchup and tomato sauce have tomato as constituents. Our barbecue sauce and mayonnaise have egg.
We can allow customers to specify they want all the egg condiments or all the tomato condiments.
We update our enum as follows:
public enum Condiments
{
TomatoSauce = 1,
Mayonnaise = 2,
BarbequeSauce = 4,
Ketchup = 8,
EggCondiments = Mayonnaise | BarbequeSauce,
TomatoCondiments = TomatoSauce | Ketchup,
}
Customers can now order a burger with tomato condiments as follows:
var burger = new Burger
{
Condiment = Condiments.TomatoCondiments
};
If we re-run the interrogation code we get the following result:
This burger has tomato sauce
This burger does not have mayonnaise
This burger does not have barbeque sauce
This burger has ketchup
We can even update our enum to allow specification of everything.
public enum Condiments
{
TomatoSauce = 1,
Mayonnaise = 2,
BarbequeSauce = 4,
Ketchup = 8,
EggCondiments = Mayonnaise | BarbequeSauce,
TomatoCondiments = TomatoSauce | Ketchup,
Everything = EggCondiments | TomatoCondiments
}
We then update our program
var burger = new Burger
{
Condiment = Condiments.Everything
};
If we re-run our logic check:
This burger has tomato sauce
This burger has mayonnaise
This burger has barbeque sauce
This burger has ketchup
What about a customer who doesn’t want any condiments?
We can update our enum to capture this, by adding an entry with a value of 0.
public enum Condiments
{
None = 0,
TomatoSauce = 1,
Mayonnaise = 2,
BarbequeSauce = 4,
Ketchup = 8,
EggCondiments = Mayonnaise | BarbequeSauce,
TomatoCondiments = TomatoSauce | Ketchup,
Everything = EggCondiments | TomatoCondiments
}
We can then update our program:
var burger = new Burger
{
Condiment = Condiments.None
};
The logic checks will print the following:
This burger does not have tomato sauce
This burger does not have mayonnaise
This burger does not have barbeque sauce
This burger does not have ketchup
When using an enum in this fashion, it is a good habit to decorate the enum
with the [Flags]
attribute.
[Flags]
public enum Condiments
{
None = 0,
TomatoSauce = 1,
Mayonnaise = 2,
BarbequeSauce = 4,
Ketchup = 8,
EggCondiments = Mayonnaise | BarbequeSauce,
TomatoCondiments = TomatoSauce | Ketchup,
Everything = EggCondiments | TomatoCondiments
}
This signals to the reader the intent of the enum.
A couple of additional things:
-
Putting
[Flags]
on your enum does not generate for you the values! You must still define them yourself. -
The compiler will allow you to shoot yourself in the foot by supplying duplicate values. This will happily compile:
public enum Condiments { TomatoSauce = 1, Mayonnaise = 1, BarbequeSauce = 1, Ketchup = 1, }
-
You can directly assign any integer value to a member expecting an
enum
. However, it is trivial to validate the values before setting them.if (!Enum.IsDefined(typeof(Condiments), 1000)) { Console.WriteLine("The value is invalid"); }
TLDR
Enums
allow for the design of fields and properties that can capture discrete or combined values into a single member of a type.
The code is in my GitHub.
Happy hacking!