InfiniteEnumFlags 0.5.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package InfiniteEnumFlags --version 0.5.0
                    
NuGet\Install-Package InfiniteEnumFlags -Version 0.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="InfiniteEnumFlags" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="InfiniteEnumFlags" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="InfiniteEnumFlags" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add InfiniteEnumFlags --version 0.5.0
                    
#r "nuget: InfiniteEnumFlags, 0.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package InfiniteEnumFlags@0.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=InfiniteEnumFlags&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=InfiniteEnumFlags&version=0.5.0
                    
Install as a Cake Tool

InfiniteEnumFlags

GitHub Nuget Nuget (with prereleases) NuGet version (InfiniteEnumFlags)

InfiniteEnumFlags gives you [Flags]-style behavior without the 32-bit or 64-bit limit of built-in .NET enums.

Use it when native C# flags are the right model, but int, long, or ulong are too small. This is common for permissions, feature switches, policy rules, and other systems where the list can grow past 64 values.

You still get named values, bitwise operators, and compact storage, but the flags are backed by a growable bit array instead of a fixed-size numeric enum.

Install

dotnet add package InfiniteEnumFlags

Define flags

Create a class that inherits from InfiniteEnum<T> and expose each value as a static Flag<T>.

-1 is reserved for None. Every other number is the zero-based bit index.

using InfiniteEnumFlags;

public sealed class Permissions : InfiniteEnum<Permissions>
{
    public static readonly Flag<Permissions> None = new(-1);      // 0      -> 0
    public static readonly Flag<Permissions> ReadUsers = new(0);  // 1      -> 1
    public static readonly Flag<Permissions> CreateUsers = new(1); // 2    -> 10
    public static readonly Flag<Permissions> DeleteUsers = new(2); // 4    -> 100
    public static readonly Flag<Permissions> ViewReports = new(100);
}

The constructor value is the bit index, not the decimal value. For example, new(2) means "turn on bit 2", which is the same idea as 1 << 2.

Flag Index Decimal value Binary shape
None -1 0 0
ReadUsers 0 1 1
CreateUsers 1 2 10
DeleteUsers 2 4 100
ViewReports 100 very large bit 100 is on

Native [Flags] enums store this shape inside an int, long, or ulong. InfiniteEnumFlags stores the same shape in a growable bit array, so high indexes like 100, 500, or 10_000 are still valid.

Combine and check flags

You can use familiar bitwise operators.

var permissions = Permissions.ReadUsers | Permissions.CreateUsers;
// binary: 1 | 10 = 11

bool canRead = permissions.HasFlag(Permissions.ReadUsers); // true
bool canDelete = permissions.HasFlag(Permissions.DeleteUsers); // false

Supported operators:

Operator Meaning Example
| add/combine flags ReadUsers | CreateUsers
& keep only shared flags permissions & required
^ toggle flags permissions ^ DeleteUsers
~ invert known bits in the current value length ~permissions

The helper methods mirror the common operators:

Method Operator Meaning
SetFlag | add flags
UnsetFlag & ~ remove flags
ToggleFlag ^ toggle flags

HasFlag checks for any overlap. This is the default because most permission-style checks ask: "does this value contain at least one of these flags?"

var required = Permissions.ReadUsers | Permissions.CreateUsers;

permissions.HasFlag(required); // true
Permissions.ReadUsers.HasFlag(required); // true

Use HasAllFlags when every requested flag must be present:

permissions.HasAllFlags(required); // true
Permissions.ReadUsers.HasAllFlags(required); // false

Set, unset, and toggle

var permissions = Permissions.None
    .SetFlag(Permissions.ReadUsers, Permissions.ViewReports);

permissions = permissions.UnsetFlag(Permissions.ViewReports);
permissions = permissions.ToggleFlag(Permissions.DeleteUsers);

Work with names

InfiniteEnum<T> can read the static flag fields you define.

var names = Permissions.GetNames().ToList();
// None, ReadUsers, CreateUsers, DeleteUsers, ViewReports

var selectedNames = Permissions.GetNames(
    Permissions.ReadUsers | Permissions.DeleteUsers);
// ReadUsers, DeleteUsers

var read = Permissions.FromName("ReadUsers");

var combined = Permissions.FromNames("ReadUsers", "ViewReports");

if (Permissions.TryFromName("DeleteUsers", out var deleteUsers))
{
    // use deleteUsers
}

All returns all non-empty flags:

var allPermissions = Permissions.All;

Store and restore values

Use ToId when you need a compact string value for storage.

IDs are canonical: equal flags produce the same ID even if they were created with different internal bit lengths. The format is normalized bytes encoded as unpadded base64url, with "0" reserved for None.

var permissions = Permissions.ReadUsers | Permissions.ViewReports;

string id = permissions.ToId();
var restored = Permissions.FromId(id);

Console.WriteLine(permissions == restored); // true

Small values stay small:

Value ID
None 0
ReadUsers AQ
CreateUsers Ag
ReadUsers | CreateUsers Aw
DeleteUsers BA

For plain padded base64 storage, use ToBase64String, ToBase64Trimmed, and FromBase64.

Scoped IDs

ToId stores only the flag value. That keeps IDs tiny and close to native enum behavior.

If values from different enum classes may share the same database column, queue, or API field, use scoped IDs:

string id = permissions.ToScopedId();
var restored = Permissions.FromScopedId(id);

The default scope is the enum class name. You can override it when multiple enum classes should intentionally share the same ID space:

string id = permissions.ToScopedId("permissions-v1");
var restored = Permissions.FromScopedId(id, "permissions-v1");

Scoped IDs are still compact, but they are not just a visible prefix. The scope changes the encoded value, so the raw value ID is not exposed inside the scoped ID.

Notes

  • Flag<T> values are immutable from public APIs.
  • Equality ignores trailing zero bits, so the same logical flags compare equal even if they were created with different internal lengths.
  • None is an empty flag set.
  • HasFlag(None) returns false because there are no bits to overlap.
  • HasAllFlags(None) returns true, because an empty requirement is always satisfied.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.
  • net10.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on InfiniteEnumFlags:

Repository Stars
youssefbennour/AspNetCore.Starter
A modular-monolith ASP.NET Core starter inspired by Evolutionary-architecture