This article will look deeper into Enums in .NET and how we can improve them with the SmartEnum library.
Let’s begin!
What Are Enums?
Enumeration types or simply Enums are value types that we can use to represent a group of named constants. By default, the underlying type of Enums in .NET is int
whose value starts at zero and is incremented by one for each additional constant.
Let’s take a look:
public enum DeveloperLevel { Junior, Regular, Senior }
Here, we define the DeveloperLevel
enumeration using the enum
keyword. We also assign three named constants – Junior
, Regular,
and Senior
, their respective values by default are 0, 1, and 2. You can check our articles on the topic for more details about enumerations.
Now we can use DeveloperLevel
:
public class Developer { public string Name { get; } public DeveloperLevel Level { get; } public Developer(string name, DeveloperLevel level) { Name = name; Level = level; } }
We define a Developer
class that has two properties – Name
of string
type and Level
of our custom DeveloperLevel
enumeration type.
Our developers should be able to write code:
public class Developer { public string Name { get; } public DeveloperLevel Level { get; } public double Productivity { get; } public Developer(string name, DeveloperLevel level) { Name = name; Level = level; Productivity = CalculateProductivity(Level); } public double WriteCode(int linesOfCode) { return linesOfCode / Productivity; } private static double CalculateProductivity(DeveloperLevel level) { switch (level) { case DeveloperLevel.Junior: return 75; case DeveloperLevel.Regular: return 125; case DeveloperLevel.Senior: return 175; default: return 0; } } }
We expand our Developer
class by adding a Productivity
property of double
type. Then we create a private
CalculateProductivity
method that returns a double
representing lines per hour. Finally, we create the WriteCode
method – it returns a double
, representing the amount of time needed to write the code.
Now, we can test our code:
var dev = new Developer("John", DeveloperLevel.Senior); Console.WriteLine($"{dev.Name} is a {dev.Level} developer."); Console.WriteLine($"{dev.Name}'s productivity is {dev.Productivity} lines/hour."); Console.WriteLine($"The developer can write 500 lines of code in {dev.WriteCode(500):F2} hours.");
Then, let’s check the result:
John is a Senior developer. John's productivity is 175 lines/hour. The developer can write in lines of code for 2.86 hours.
We see that John is a senior developer with an expected Productivity
of 175 lines of code per hour, and can write 500 lines in 2.86 hours.
A problem we have here is that Productivity
is directly tied to DeveloperLevel
, so it’s not the Developer
‘s class responsibility to calculate it.
Let’s see how we can improve that.
Install the SmartEnum Package
Let’s install the library via the .NET CLI:
Install-Package Ardalis.SmartEnum
We are ready to roll!
How to Use SmartEnum to Improve Enums?
Now that we have installed the package, let’s see what we can do with it:
public sealed class DeveloperLevel : SmartEnum<DeveloperLevel> { public DeveloperLevel(string name, int value) : base(name, value) { } }
We create a new DeveloperLevel
class that inherits from SmartEnum<T>
and for T
we pass the new class name. We mark the class as sealed
as we don’t want other classes to inherit from it. Then we implement a constructor that takes in two parameters – a string
for the name of the enumeration and an int
for its value. Finally, we call the SmartEnum
‘s base constructor and pass it the two parameters.
This is the base for our new, smart enumeration.
Next, we expand it:
public sealed class DeveloperLevel : SmartEnum<DeveloperLevel> { public static readonly DeveloperLevel Junior = new DeveloperLevel(nameof(Junior), 1, 75); public static readonly DeveloperLevel Regular = new DeveloperLevel(nameof(Regular), 2, 125); public static readonly DeveloperLevel Senior = new DeveloperLevel(nameof(Senior), 3, 175); public double Productivity { get; } public DeveloperLevel(string name, int value, double productivity) : base(name, value) { Productivity = productivity; } }
First, we create a Productivity
property of double
type with only a getter, no setter. Then we pass a productivity
parameter of double
type to our constructor and assign it to Productivity
. Finally, we create three static
readonly
fields of DeveloperLevel
type – Junior
, Regular
, and Senior
, and we initialize them with new instances of DeveloperLevel
passing the name
, value,
and productivity
of our developer levels.
We can now update our Developer
class:
public class Developer { public string Name { get; } public DeveloperLevel Level { get; } public Developer(string name, DeveloperLevel level) { Name = name; Level = level; } public double WriteCode(int linesOfCode) { return linesOfCode / Level.Productivity; } }
We remove all mentions of Productivity
in our class, as well as the CalculateProductivity
method. Then we update the returned result of the WriteCode
method by replacing Productivity
with Level.Productivity
.
At this point, we can go back to our Program
class:
var dev = new Developer("John", DeveloperLevel.Senior); Console.WriteLine($"{dev.Name} is a {dev.Level} developer."); Console.WriteLine($"{dev.Name}'s productivity is {dev.Level.Productivity} lines/hour."); Console.WriteLine($"The developer can write 500 lines of code in {dev.WriteCode(500):F2} hours.");
The only update we need here is replacing Productivity
with Level.Productivity
.
Finally, let’s run the code and examine the result:
John is a Senior developer. John's productivity is 175 lines/hour. The developer can write 500 lines of code in 2.86 hours.
The output is the same as before but now our code is cleaner and has a better separation of concerns.
Helpful SmartEnum Methods and Properties That Further Improve Enums
This is not everything we can do with the SmartEnum
library – we also have several methods and properties that expand our options:
var juniorFromName = DeveloperLevel.FromName("Junior"); var juniorFromValue = DeveloperLevel.FromValue(1);
Here, we use two pre-defined methods – FromName
and FromValue
that we can use to create an enumeration of DeveloperLevel
type. If the value passed to the methods is correct and an enumeration can be matched we get an instance of DeveloperLevel.Junior
type, if not – a SmartEnumNotFoundException
is thrown.
Let’s check if our two variables are of the same type:
Console.WriteLine(juniorFromName == juniorFromValue);
This will write True to the console and confirm that both variables are of DeveloperLevel.Junior
type.
Throwing an Exception
when there is no matching enumeration and stopping our application is not ideal. So, let’s improve that:
if (DeveloperLevel.TryFromName("Regular", out var regularFromName)) { Console.WriteLine($"Created enumeration from name: {regularFromName.Name}"); } if (DeveloperLevel.TryFromValue(2, out var regularFromValue)) { Console.WriteLine($"Created enumeration from value: {regularFromValue.Name}"); }
We wrap the TryFromName
and TryFromValue
methods in if
statements. The first parameter we pass to those methods is the enumeration name. As the method returns true
or false
depending on the success of the parsing, we need a way to get a variable if it was successful. We achieve that by passing a second parameter to the methods using the out
keyword. If parsing from name and/or value was successful, the regularFromName
and/or regularFromName
variables of DeveloperLevel.Regular
type will be created:
Created enumeration from name: Regular Created enumeration from value: Regular
Running our application confirms that we successfully created variables of DeveloperLevel.Regular
type, parsing both from name and value.
We can also access the list of enumerations with the List
property:
Console.WriteLine($"The {nameof(DeveloperLevel)} has {DeveloperLevel.List.Count} different enumerations:"); foreach (var enumeration in DeveloperLevel.List ) { Console.WriteLine(enumeration.Name); }
The List
property returns an IReadOnlyCollection
type which enables us to use the Count
property as well. As we get a variable of IReadOnlyCollection
type, we can iterate over its elements and interact with them.
Our code produces:
The DeveloperLevel has 3 different enumerations: Junior Regular Senior
We get the exact number of enumerations and we successfully iterate over them and print their names.
Conclusion
In this article, we learned about Enums and how to improve them with the SmartEnum
library. Plain Enums can be useful in simple cases but when we work with complex logic that is directly related to the enumeration type it is always better to use advanced Enums which we can create with SmartEnum
.