Open-source project
ardalis/SmartEnum avatar
ardalis/SmartEnum

Ardalis.SmartEnum: a type-safe, object-oriented replacement for C# enums

A base class for quickly and easily creating strongly typed enum replacements in C#.

2,443 stars182 forksC#MIT

At a glance

What is it?
SmartEnum turns each enum member into a sealed class instance that can carry behaviour, string values and metadata. It suits domain-driven C# codebases that need more than an int constant, and it asks you to accept extra allocation and serialization plumbing.
Who is it for?
Adopt SmartEnum when your enum members need behaviour, string persistence or validation and you are willing to register the serializer and EF Core converters that go with it. Skip it for small internal flag sets, tight loops over millions of values, or any code path where a plain C# enum already reads correctly.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 156 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What a C# enum cannot do, and who feels it

A C# enum is a named integer. That is all it is. You cannot attach a display label, a code used by an external system, or a method that computes something from the member. The usual workarounds are a switch statement somewhere else in the codebase, a static dictionary keyed by the enum, or a DescriptionAttribute plus reflection to read it back. Each of those spreads knowledge about one concept across several files.

Ardalis.SmartEnum addresses that gap. The README describes it as "a type-safe object-oriented alternative to C# enum", citing Jon Skeet's 2006 class-enum post. Instead of a value type with a fixed set of names, you get a sealed class with one static readonly field per member. Because members are objects, they can hold extra fields, expose methods, and participate in the type system the way any other class does.

The audience is C# teams doing domain-driven design, where a concept like OrderStatus or PaymentMethod is expected to carry rules rather than just be compared. If your enums are internal and never leave a method, the base class adds nothing. The project's own topics list clean architecture and design patterns, which matches that audience.

The mechanism: sealed class, static fields, lookup dictionaries

You declare a class that inherits from SmartEnum<TEnum>, where TEnum is the type being declared. Each member is a public static readonly field constructed with a name and a value. The base class keeps the name and value, and the README shows lookups through FromName() and FromValue(), plus a List property that enumerates the declared members.

Those lookups are not free. FromName and FromValue imply a dictionary built from the static fields, so a lookup is a hash probe rather than a compile-time constant. That is the central trade-off: you gain behaviour and metadata, and you pay for it with an object per member and a lookup instead of an integer comparison. For a status field read a few times per request this is irrelevant. For a hot loop over millions of rows it is not.

The README example shows the shape directly:

csharp
using Ardalis.SmartEnum;

public sealed class TestEnum : SmartEnum<TestEnum>
{
    public static readonly TestEnum One = new TestEnum(nameof(One), 1);
    public static readonly TestEnum Two = new TestEnum(nameof(Two), 2);
    public static readonly TestEnum Three = new TestEnum(nameof(Three), 3);

    private TestEnum(string name, int value) : base(name, value) { }
}

Note the private constructor. Members can only be created inside the class, so the set stays closed, which is the property people actually want from an enum. The README also documents a Switch helper, a SmartFlagEnum variant for bitwise combinations with a BitWiseOrOperator, a case-insensitive string enum, and a name validation attribute.

Installing Ardalis.SmartEnum and defining a first member

The framework ships as NuGet packages. The README says most projects only need the base package. From the Package Manager Console the command is:

bash
Install-Package Ardalis.SmartEnum

The README states the latest version supports .NET 8 and NetStandard 2.0. Sub-packages are installed only when you need them, and the README lists the full set:

bash
Install-Package Ardalis.SmartEnum.AutoFixture
Install-Package Ardalis.SmartEnum.JsonNet
Install-Package Ardalis.SmartEnum.SystemTextJson
Install-Package Ardalis.SmartEnum.Utf8Json
Install-Package Ardalis.SmartEnum.MessagePack
Install-Package Ardalis.SmartEnum.ProtoBufNet
Install-Package Ardalis.SmartEnum.EFCore
Install-Package Ardalis.SmartEnum.ModelBinding
Install-Package Ardalis.SmartEnum.Dapper

After adding the base package, define a class as shown in the previous section and reference a member by its static field. A method that takes the type now accepts only declared members, and the compiler rejects a raw integer. The README's usage section then walks through List, FromName(), FromValue(), ToString() and Switch, in that order, which is a reasonable order to read them in when you are wiring your first type up.

One practical note: the README does not give a dotnet add package command line, only the Package Manager Console form. The equivalent CLI invocation exists in the .NET SDK, but it is not in the README, so treat the form above as the documented one.

Serialization and persistence are separate packages, not defaults

This is the part that surprises people. A SmartEnum type is a class, so a default serializer will try to walk its properties rather than write a single value. The repository splits the fix into per-serializer packages: SmartEnum.JsonNet for Newtonsoft.Json, SmartEnum.SystemTextJson, SmartEnum.Utf8Json, SmartEnum.MessagePack and SmartEnum.ProtoBufNet. There is a SmartEnum.EFCore package for Entity Framework Core 2.1 or higher, and a SmartEnum.Dapper package with a DapperSmartEnum type for Dapper.

That split is honest about the problem but it does mean adoption is not a one-line change. If your project uses System.Text.Json and you install only the base package, you will get surprising JSON. You have to know which serializer you use and add the matching package. The README documents each one in its own subsection, including a FromValueToString() helper and the BitWiseOrOperator for the flag variant.

There is no single package that covers everything, and the README does not describe a fallback for serializers outside that list. If your stack uses something the list does not cover, you are writing a converter yourself. That is a real cost to weigh before converting an existing enum that already flows through an API boundary.

Where SmartEnum is the wrong tool

The clearest wrong case is a flag set used in tight arithmetic. SmartFlagEnum exists and supports bitwise OR, but you are still working with objects and lookups rather than ints, and the README's flag section is a smaller part of the documentation than the base usage. If your code is mostly bitmask checks, a plain [Flags] enum is simpler and faster.

The second wrong case is a value that crosses a wire format you do not control. SmartEnum's identity is the name and value you assign, and the serialization packages decide how that becomes JSON or a database column. If a partner API expects the integer 2 and your converter writes the string "Two", you have a mismatch that no amount of type safety fixes. Check the converter behavior per serializer before converting an existing contract.

The third is a throwaway internal enum. If the members never need a label, a rule or a validation message, SmartEnum is extra machinery. The README's list of sub-packages is a fair proxy for the surface area you take on.

A note on maintenance: the repository is not archived, and the last push was on 2026-04-29. The most recent release listed is SmartEnum 8.2 from 2024-11-19, so the release cadence and the commit cadence are not the same thing.

How it compares to other C# enum alternatives

The README's own reference is Jon Skeet's class-enum pattern, and SmartEnum is essentially that pattern packaged with lookups, flags, validation and serializer integrations. Writing the pattern by hand gives you full control and no dependency, but you reimplement FromName, FromValue and the static list every time, and you get no EF Core or Dapper support.

Thinktecture appears in the related searches as another option in this space. The difference in approach is that Thinktecture's source generator emits the boilerplate at compile time from an attribute or a partial class, whereas SmartEnum is a runtime base class you inherit from. A generated approach can avoid some runtime lookup cost and can produce struct-based types; SmartEnum's members are objects created by static field initializers. Which fits depends on whether you prefer a dependency plus inheritance or a source generator plus build-time code.

The related searches also surface "C# string enum alternative" and "C# record vs enum". A record gives you value equality and a constructor, but it does not give you a closed set of members or FromName lookups. SmartEnum's contribution is the closed set plus the lookup surface, not the ability to hold data.

Licence, upgrade cost and what to check before adopting

The repository is MIT licensed, which permits commercial use and modification provided the copyright notice and permission notice are retained. That is a permissive licence, and the practical implication for most teams is that the package can ship inside a closed-source product. This is not legal advice; confirm the terms with your own counsel if the distinction matters to you.

Upgrade cost is mostly driven by the serializer and ORM packages. The base package changes slowly, and the version history shown here runs 8.0, 8.1 and 8.2, with 8.0 in January 2024 and 8.2 in November 2024. Since the sub-packages version independently, a major bump in one serializer package can land without touching the base. The README does not document a rollback procedure or a compatibility matrix between base and sub-package versions.

Before converting an existing enum, check three things: which serializer your service actually uses, whether the matching sub-package exists at the version you need, and how the value is stored in your database today. The README's EF Core section is the place to start for the third, since it covers persisting with EF Core 2.1 or higher and the SmartEnum.EFCore package specifically.

Editorial conclusion

Adopt SmartEnum when your enum members need behaviour, string persistence or validation and you are willing to register the serializer and EF Core converters that go with it. Skip it for small internal flag sets, tight loops over millions of values, or any code path where a plain C# enum already reads correctly. Before committing, verify that the sub-package for your serializer exists at the version you need, and confirm how your database stores the value column, because SmartEnum's base package alone does not decide that.

Frequently asked questions

What is SmartEnum in C#?

It is a base class that lets you declare a sealed class whose static readonly fields act as enum members, so each member can carry a name, a value and behaviour. The README describes it as a type-safe object-oriented alternative to the C# enum keyword.

What is an enum in simple terms?

In this project's terms, a C# enum is a named integer with a fixed set of names, and the README positions SmartEnum as the object-oriented alternative to it. SmartEnum keeps the fixed set but makes each member a class instance instead.

Why should I use enum?

The README's answer is type safety plus behaviour: a SmartEnum member can hold extra fields and expose methods, which a plain C# enum cannot. Lookups such as FromName() and FromValue() are also built in, along with a Switch helper and a SmartFlagEnum variant.

How do I create an enum with Ardalis.SmartEnum?

Declare a sealed class that inherits from SmartEnum<TEnum>, add one public static readonly field per member, and give the class a private constructor that calls base(name, value). The README shows this with a TestEnum type holding One, Two and Three.

How do I get the name of a SmartEnum member?

The base class exposes FromName() and FromValue() for lookups in both directions, and ToString() is documented in the usage section. The README lists List, FromName(), FromValue(), ToString() and Switch as the core usage topics.

Official sources

  1. ardalis/SmartEnum on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ardalis-smartenum.svg)](https://hysenlabs.com/projects/ardalis-smartenum)