Enums are widely used in the .NET projects. They can save some roundtrips to the database and are self-documented, in theory. I’d like to explain how to and how no to use enums to avoid the butterfly effect in the repo.
The Bright Side
- Type checked parameters
Enums offer static type checking in parametrized methods restricting the acceptable values.
- Readability
They also provide a more readibly overlay over the magic numbers used in the system.
- Code completion
All IDEs handle the code completion well, so it slightly speeds up the development
The Gray Side
- Limiting the database configuration queries
Here’s where the fun begins. It’s a given that keeping configuration data in an enum can save some roundtrips to the database, especially if the query are fired often. However this almost always breaks the rule of restraining the enums to the values that never change. An example: if we decide to keep order statuses in an enum, they’ll probably change in time, which will force us to update the type members. Worse, what if one of the clients will demand a new status. It’s worth mentioning that Enums are not derivable, so without extension methods it’s not easible achievable to create some inheritance. Another question arrives: should the unique status be a part of the application or a unique one per client, but that’s more of a architectural approach…
I’d recommend to stay away from enums in such situation and create separate classes with inheritance and caching.
The Dark Side
- Enums are magic numbers in database or require weird casting
If you use the underlying enum values in the database then quering and saving the data is easy, however you end up in the magic number in the persistance layer. I’ve seen examples in which the enum names were stored in the db, however then we need to use reflection for getting the property value, which is not the best solution in terms of performance and forces the devs to write some extension methods or repeat the following code:
public enum Area
{
First,
Second,
[Description("Thirty!")]
Third
}
public static int EnumToInt(Area area)
{
return (int)area;
}
public static Area IntToEnum(int intValue)
{
return (Area)intValue;
}
public static Area StringToEnum(string stringValue)
{
return (Area)Enum.Parse(typeof(Area), stringValue);
}
public static int StringToInt(string stringValue)
{
var area = StringToEnum(stringValue);
return EnumToInt(area);
}
public static string EnumToDescription(Area area)
{
Type type = area.GetType();
var memInfo = type.GetMember(type.GetEnumName(area));
var descriptionAttribute = memInfo[0]
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.FirstOrDefault() as DescriptionAttribute;
return descriptionAttribute?.Description;
}
public static Area DescriptionToEnum(string stringValue)
{
Type type = typeof(Area);
foreach (var field in type.GetFields())
{
var attribute = Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null)
{
if (attribute.Description == stringValue)
return (Area)field.GetValue(null);
}
else
{
if (field.Name == stringValue)
return (Area)field.GetValue(null);
}
}
throw new ArgumentException("Not found.", "description");
}
- Enums as anti pattern for type checking
Adding enums for types with different behavior immediately leads to creating ifs with type checking. This can be replaced with polymorphic behavior of derived type. A situation like this is a code-smell and should be refactored.
Conclusion
What should be used for:
- Provide a meaningful name for values that never change (days of week)
What shouldn’t be used for:
- For types, which require different behaviour (customer types, contracts – instead use polimorphism)
- For expendable values that rely on their string values (colors)
Bonus: [Flags]
If we would like to set a range of acceptable values, Flags attribute offer such possibility. The requirement is that each enum value should have the value of the corresponding bit position i.e.
public enum DaysOfWeek {
None = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64
}
Note: binary formatting will also work.
Then using the pipe operator we can pass the enums as parameter:
public void DoSomething(DaysOfWeek) {};
DoSomething(DaysOfWeek.Saturday | DaysOfWeek.Sunday);
DoSomething(DaysOfWeek.Saturday ^ DaysOfWeek.Sunday);
There’s a limitation to the 32 values due to the memory limit on the type, but if you store 32 values in an enum, probably memory isn’t your main problem.