This article will look deeper into Enums in .NET and how we can improve them with the SmartEnum library.

To download the source code for this article, you can visit our GitHub repository.

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 DeveloperLevelenumeration 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 privateCalculateProductivity method that returns a double representing lines per hour. Finally, we create the WriteCodemethod – 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 productivityparameter of double type to our constructor and assign it to Productivity. Finally, we create three static readonlyfields 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

This content is available exclusively to members of Code's Patreon at $0 or more.