C# Smart Enums: advanced
Source: Dev.to

The Problem: The Copy-Paste Trap
By Part 2, we had a high‑performance O(1) dictionary lookup. However, if your application has dozens of status types (Order, Payment, User, etc.), you might find yourself copy‑pasting that same dictionary and lookup logic repeatedly.
A Suggestion: An Advanced Generic Base Class
To keep your code DRY (Don’t Repeat Yourself), we can move the “heavy lifting” into a single abstract base class. This allows your specific status classes to focus purely on defining their values while inheriting all the optimized lookup logic for free.
The implementation uses the Curiously Recurring Template Pattern (CRTP). It ensures that each specific Smart Enum (like ProductStatus) maintains its own private dictionary in memory, preventing data collisions between different types.
The Implementation
We define a simple interface to ensure every value has an Id, followed by a base class providing multiple ways to access your data safely.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
public interface ISmartEnumValue
{
int Id { get; }
}
public abstract class SmartEnum
where TValue : class, ISmartEnumValue
where TSelf : SmartEnum
{
private static readonly Dictionary _lookup = new();
protected static TValue Register(TValue value)
{
_lookup[value.Id] = value;
return value;
}
// Forces the static constructor of the child class to run immediately
public static void Initialize()
{
RuntimeHelpers.RunClassConstructor(typeof(TSelf).TypeHandle);
}
// 1. Strict Get: Throws if ID is missing (Use when existence is mandatory)
public static TValue Get(int id)
{
if (_lookup.TryGetValue(id, out var value))
return value;
throw new KeyNotFoundException(
$"Value with ID {id} not found in {typeof(TSelf).Name}");
}
// 2. Safe Get: Returns null if ID is missing (Use for optional data)
public static TValue? GetOrDefault(int id) => _lookup.GetValueOrDefault(id);
// 3. Pattern Matching Get: Returns bool (Standard .NET 'Try' pattern)
public static bool TryGet(int id, out TValue? value)
{
return _lookup.TryGetValue(id, out value);
}
public static bool Exists(int id) => _lookup.ContainsKey(id);
public static IEnumerable GetAll() => _lookup.Values;
}
Usage Examples
Once initialized, you have total flexibility in how you consume your Smart Enums.
// Initialize once at startup (e.g., Program.cs)
ProductStatus.Initialize();
// Example 1: Strict access (expects ID to exist)
var status = ProductStatus.Get(1);
Console.WriteLine(status.Description);
// Example 2: Safe access with null check
var maybeStatus = ProductStatus.GetOrDefault(99);
if (maybeStatus != null) { /* Do something */ }
// Example 3: Pattern matching for clean branching
if (ProductStatus.TryGet(2, out var foundStatus))
{
Console.WriteLine($"Found: {foundStatus?.Description}");
}
Why This Is a Robust Architectural Choice
- Flexible Consumption – Choose between exceptions, nulls, or booleans based on your business flow.
- Strict Type Safety – The
TSelfconstraint ensuresProductStatus.Get()returns aProductStatusValuedirectly, with no casting required. - No Reflection – By using
Initialize(), we avoid the performance overhead of assembly scanning. - Zero Boilerplate – Specific enum classes focus entirely on the data, while the engine remains encapsulated in the base.
Try It Yourself
Version Note
This advanced implementation requires .NET 6 or higher. The use of CRTP and modern generic constraints ensures type safety and performance in modern C# environments.
Further Reading & Resources
- Microsoft Docs: Generic Constraints
- Ardalis.SmartEnum: GitHub Repository – The industry standard for production‑grade Smart Enums.