DEV Community

Cover image for C# Smart Enums: escape magic number hell
helder sousa
helder sousa

Posted on

C# Smart Enums: escape magic number hell

The Problem: Magic Number Hell

We've all seen code like this:

if (product.StatusId == 2) 
{
    // What is 2? Pending? Deleted? Available?
    product.StatusName = "?"; 
}
Enter fullscreen mode Exit fullscreen mode

This is Magic Number Hell. It’s hard to read, impossible to maintain, and a magnet for bugs. You have to search the codebase and figure out the meaning:

This code has several issues:

  • The number 2 has no meaning without context
  • The description will likely be duplicated string everywhere
  • It leads to errors when setting id or description
  • Refactoring is a struggle

Let's look at the traditional ways often adopted, and finally see the Smart Enum pattern and why it would be the best option.


Traditional approaches : why they fall short ?

const Properties

Following you see a valid often used approach using constants:

public static class Statuses
{
    public const int Available = 1;
    public const int Unavailable = 2;

    public const string AvailableDescription = "Available";
    public const string UnavailableDescription = "Unavailable";
}

if (product.StatusId == Statuses.Available)
{
    product.StatusName = Statuses.AvailableDescription;
}
Enter fullscreen mode Exit fullscreen mode

Downsides:

  • Descriptions are separate from IDs
  • Can't iterate
  • Not type-safe

enum with Attributes

Built-in enum is better than const, and often used as well. See the following example of its usage:

public enum Statuses
{
    [Description("Available")]
    Available = 1,
    [Description("Unavailable")]
    Unavailable = 2
}

// Usage
product.StatusId = (int)Statuses.Available;
product.StatusName = GetDescription(Statuses.Available);
Enter fullscreen mode Exit fullscreen mode

But it also has downsides:

  • Reflection is slow when used to retrieve and item description (the following method shows an example using reflection)
  • Alternatives to Reflection are messy helper methods
  • It´s verbose
  • It´s not LINQ-friendly
// A reflection version method for retrieving the item description
public static string GetDescription(Statuses status)
{
    var field = status.GetType().GetField(status.ToString());
    var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
    return attribute?.Description ?? status.ToString();
}
Enter fullscreen mode Exit fullscreen mode

The Solution: Smart Enum pattern with C# Records

Although traditional approaches are valid, Smart Enum pattern offers a modern way to solve the magic number nightmare and without any downside. See how it looks like:

public record StatusValue(int Id, string Description);

public static class Status
{
    public static readonly StatusValue Pending = new(1, "Pending Approval");
    public static readonly StatusValue Available = new(2, "Available for Sale");
    public static readonly StatusValue OutOfStock = new(3, "Out of Stock");

    public static readonly StatusValue[] All = { Pending, Available, OutOfStock };
}

Enter fullscreen mode Exit fullscreen mode

How to use it today

Considering the prior Smart Enum sample type, its usage would produce some lines like the following:

product.StatusId = Status.Available.Id;
product.StatusName = Status.Available.Description;

if (product.StatusId == Status.Available.Id) { ... }
Enter fullscreen mode Exit fullscreen mode

Even without complex architecture, you can use standard LINQ to clean up your logic:

// Retrieve safely
var status = Status.All.SingleOrDefault(x=>x.Id == userInput);
if (status!=null)
{
   product.StatusId = status.Id;
   product.StatusDescription = status.Description;+
}

// Compare with confidence
if (product.StatusId == Status.Available.Id) { ... }

// dropdown list
var dropdownList = Status.All.Select(s => new
{ 
        Key = s.Id, 
        Value= s.Description 
});

Enter fullscreen mode Exit fullscreen mode

Comparison

Approach Type-safe LINQ-ready Performance Maintainable Metadata
Magic numbers No No Fast No No
enum Yes Sort of Slow Sort of Limited
const Sort of No Fast Sort of
Smart Enum Yes Yes Fast Yes Rich

Try It yourself


Further Reading


Version Note: The examples in this series require .NET 6 or higher

Top comments (0)