Enumerations are a very primitive type that are frequently overused. In many scenarios, actual objects are a better choice.
Sponsor - DevIQ
Thanks to DevIQ for sponsoring this episode! Check out their list of available courses and how-to videos.
Show Notes / Transcript
Enums are an extremely common construct in applications. They provide a simple way to give labels to numeric values. They're especially useful for efficiently capturing a set of flag values by using binary AND and OR operations on values set to powers of 2. However, as primitive value types, they don't have the capability to add behavior to the values they represent, and this often results in a particular flavor of the primitive obsession code smell that I discussed in episode 12.
One of the first signs that you're stretching the limits of an enum in C# is if you find that you want to display the names associated with the values to the user, and some of the names should have spaces in them when displayed. For example, you might have a Roles enum that includes a SalesRepresentative name. If you display that in a dropdownlist in the UI, you'll want to have a space between Sales and Representative. There are a few hacky ways to achieve this. The first would be to parse the name of the enum and insert spaces anywhere you find capital letters in the middle of the string. Another common one is to add an attribute that contains the user-friendly version of the enum's name, and if this attribute is present, use it when displaying the enum's name. Both of these can work, but they're not ideal. They both require more code outside of the enum, making it harder to work with, and scattering logic related to the enum into other types.
While we're on the topic of displaying enum values to end users, another fairly common requirement in this area is to control which enum options are displayed to the user. Once again, you can use attributes to control this behavior, or maybe even some kind of naming convention for the enum labels (maybe add a Visible or Hidden suffix and then strip off the suffix when displaying the name). As you can guess, both of these approaches just lead you further down the path of cluttering up your non-enum code to accommodate the lack of behavior within the enums themselves. What you really need is a better abstraction.
The pattern I favor is the SmartEnum class, also known as the Strongly Typed Enum Class. With this pattern, you start with a class definiton that includes the basic capabilities of an enum type, such as having a simple name and value. Then, you define the set of available options as static properties on the class. For example, if you were creating a Roles enumeration class, you would add static properties on the Roles class for things like Administrator or SalesRepresentative. These static properties would be of type Roles (or Role, as you prefer). Working with these static instances mirrors working with enums. You can simply type Roles (dot) and your IDE will show you the set of static properties that represent the possible options, just the same as an enum.
Since you're representing your options as a class, you now have the ability to add any behavior you require. If you need to display the value in a certain way, you can add a property or method to do so. If you need to add metadata that will determine when or whether a particular option is visible or available to a given user, you can add this as well. When you do, the business logic you're adding is encapsulated within the enumeration class, rather than spread throughout your user interface code.
If you're looking to get started with this approach, I've created a GitHub repo and Nuget package at Ardalis.SmartEnum. I've also written several articles over the years on this topic that I'll add to the show notes for this episode, which you'll find at weeklydevtips.com/014.