CodeChops.MagicEnums 3.3.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package CodeChops.MagicEnums --version 3.3.0
NuGet\Install-Package CodeChops.MagicEnums -Version 3.3.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="CodeChops.MagicEnums" Version="3.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add CodeChops.MagicEnums --version 3.3.0
#r "nuget: CodeChops.MagicEnums, 3.3.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.
// Install CodeChops.MagicEnums as a Cake Addin
#addin nuget:?package=CodeChops.MagicEnums&version=3.3.0

// Install CodeChops.MagicEnums as a Cake Tool
#tool nuget:?package=CodeChops.MagicEnums&version=3.3.0

Magic Enums

Fast, customizable, and extendable enums for C# with a clean API, see advantages.

Basic examples

Default usage

using CodeChops.MagicEnums;

public record Level : MagicEnum<Level> // Default: int.
{
    public static readonly Level Low    = CreateMember();   // Default: 0
    public static readonly Level Medium = CreateMember(1);  // Explicit: 1	
    public static readonly Level High   = CreateMember();   // Implicit: 2
}

Extended usage

public record Vehicle(int WheelCount) : MagicEnum<Vehicle>
{
    public static readonly Type.Bicycle    Bicycle     = CreateMember<Type.Bicycle>();
    public static readonly Type.MotorCycle MotorCycle  = CreateMember<Type.MotorCycle>();
    public static readonly Type.Car        FuelCar     = CreateMember<Type.Car>(() => new(EmitsCo2: true));
    public static readonly Type.Car        ElectricCar = CreateMember<Type.Car>(() => new(EmitsCo2: false));
    
    public static class Type
    {
        public record Bicycle()          : Vehicle(WheelCount: 2);
        public record MotorCycle()       : Vehicle(WheelCount: 2);
        public record Car(bool EmitsCo2) : Vehicle(WheelCount: 4);
    } 
}

Functionality

Terminology used in this documentation:

  • An enum has one or multiple members.
  • Each member has a name and a value.
  • A member name has to be unique across all members of an enum.
  • A member value does not have to be unique across all members.
  • The type of the value depends on the chosen enum type, see: enum types.

Magic enums behave like the default .NET enum implementation:

  • They use int as default for the member value.
  • Members can be found by searching for their name or value.
  • More than one member name can be assigned to the same value but only one value can be assigned to a member name.
  • The value of members can be omitted (when using the default numeric enums). If omitted, it automatically increments the value of each member.
  • Members, member names or member values can easily be enumerated.
  • Flag enums are supported.
  • Strongly typed enum members, so pattern matching can be used.

Advantages

Besides the default .NET enum behaviour, MagicEnums offer more features than the default .NET enum implementation:

  • Extendability:
    • Inheritance is supported. This way enums can also be extended in other assemblies.
    • Partial enums are supported.
    • Custom methods and properties can be added to the enums.
  • Different types of enums are supported:
  • Members can be added at runtime, if necessary. This is thread-safe.
  • Members with the same value can be looked up easily. Something which is not supported in default C# enums.
  • Optimized, and therefore fast member registration and lookup, including a fast ToString. For extra optimization, see optimization.
  • Serialization to/from JSON is supported.
  • Members can be auto-discovered. This removes the need to keep track of used/unused enum-members. See auto member discoverability.

API

Method Description Accessibility
CreateMember() Creates a new enum member and returns it. protected
GetEnumerator() Gets an enumerator over the enum members. public
GetMembers() Gets an enumerable over:<br/>- All enum members, or<br/>- Members of a specific value: Throws when no member has been found. public
GetValues() Gets an enumerable over the member values. public
TryGetMembers() Tries to get member(s) by value. public
TryGetSingleMember() Tries to get a single member by name / value.<br/>Throws when multiple members of the same value have been found. public
GetSingleMember() Gets a single member by name / value.<br/>Throws when not found or multiple members have been found. public
GetUniqueValueCount() Gets the unique member value count. public
GetMemberCount() Gets the member count. public
GetDefaultValue() Gets the default value of the enum. public
GetOrCreateMember() Creates a member or gets one if a member already exists. protected

Enum types

Number enums

Number enums (default) have a numeric type as value.

  • Can be created by implementing MagicEnum<TSelf, TNumber>.
  • If TNumber is omitted, int will be used as type: MagicEnum<TSelf>.
  • TNumber can be of any type that are also supported by the default .NET implementation: byte, sbyte, short, ushort, int, uint, long, or ulong.
  • Unlike the default C# .NET implementation, decimal is also supported.
  • Implicit and explicit value declaration are supported, see the example below.
Example
using CodeChops.MagicEnums;

public record StarRating : MagicEnum<StarRating>
{
    public static readonly StarRating One   = CreateMember(1);
    public static readonly StarRating Two   = CreateMember();	
    public static readonly StarRating Three = CreateMember();
    public static readonly StarRating Four  = CreateMember();
    public static readonly StarRating Five  = CreateMember();
}

The example creates an enum with an int as member value. The value of the first member is explicitly defined. Other values are being incremented automatically, because they are defined implicitly.

using CodeChops.MagicEnums;

public record EurUsdRate : MagicEnum<EurUsdRate, decimal>
{
    public static readonly EurUsdRate Average2021 = CreateMember(0.846m);
    public static readonly EurUsdRate Average2020 = CreateMember(0.877m);
    public static readonly EurUsdRate Average2019 = CreateMember(0.893m);
    public static readonly EurUsdRate Average2018 = CreateMember(0.848m);
}

This example shows the usage of a decimal as member value.

Flags enums

using CodeChops.MagicEnums;

public record Permission : MagicFlagsEnum<Permission>
{
    public static readonly Permission None          = CreateMember(); // 0
    public static readonly Permission Read          = CreateMember(); // 1 << 0 
    public static readonly Permission Write         = CreateMember(); // 1 << 1
    public static readonly Permission ReadAndWrite  = CreateMember(Read | Write);
}

This example shows the usage of a flags enum. Note that member ReadAndWrite flags both Read and Write. Flags enums offer extra methods:

  • GetUniqueFlags(): Gets the unique flags of the provided value.
  • HasFlag(): Returns true if a specific enum member contains the provided flag.

String enums

Sometimes you only need an enumeration of strings (for example: names). In this case the underlying numeric value is not important. Magic string enums helps you achieving this:

  • Can be created by implementing MagicStringEnum<TSelf>.
  • Ensure that the values of the members are equal to the name of the members. This can be manually overriden, if necessary.
  • Prohibit incorrect usage of numeric values when they are not needed.
  • Remove the need to keep track of (incremental) numeric values.
  • When members are generated, they show an automatic warning in the comments that the members shouldn't be renamed. See member generation.
Example
using CodeChops.MagicEnums;

public record ErrorCode : MagicStringEnum<ErrorCode>
{
    public static readonly ErrorCode EndpointDoesNotExist   = CreateMember();
    public static readonly ErrorCode InvalidParameters      = CreateMember();	
    public static readonly ErrorCode NotAuthorized          = CreateMember();
}

In this example 3 members are created whereby their value equals their name.

Custom enums

Custom enums can also be created. They offer a way to create an enum of any type that you prefer:

  • Can be created by implementing MagicCustomEnum<TSelf, TValue>.
  • TValue should be a struct which implements IEquatable and IComparable.
  • A custom value type can easily be generated using the Value object generator which is included in the Domain Modeling-package.
  • Two dogfooding examples of the usage custom enums:

Pattern matching

To achieve pattern matching, you can do the following below.

var message = level.Name switch
{
    nameof(Level.Low)       => "The level is low.", 
    nameof(Level.Medium)    => "The level is medium.",
    nameof(Level.High)      => "The level is high.",
    _                       => throw new UnreachableException($"This should not occur.")
};

In this example, the enum from the default usage example is used.

Another way is to define the types in an inner class and use them as the type of an enum member:

var speedingFineInEur = vehicle switch
{
    Vehicle.Type.MotorCycle => 60,
    Vehicle.Type.Car        => 100,
    _                       => 0,
};

In this example, the enum from the extended usage example is used.

Member generation

Members can be generated automatically, this way members don't have to be manually defined in the enum.

Attributes

Members can be generated using the EnumMember attribute. The valueand a comment can be provided. The members are automatically generated using C# source generators.

Example
[EnumMember("Low")]
[EnumMember("Medium", 1)]
[EnumMember("High", comment: "Has a value of 2.")]
public partial record Level : MagicEnum<Level>;

The following code will be generated:

// <auto-generated />
#nullable enable

using System;
using System;
using CodeChops.MagicEnums;

namespace CodeChops.MagicEnums.UnitTests.SourceGeneration.AttributeMembers;

/// <summary>
/// <list type="bullet">
/// <item><c><![CDATA[ Low    = 0 ]]></c></item>
/// <item><c><![CDATA[ Medium = 1 ]]></c></item>
/// <item><c><![CDATA[ High   = 2 ]]></c></item>
/// </list>
/// </summary>
public partial record class Level
{
    /// <summary>   
    /// <c><![CDATA[ (value: 0) ]]></c>
    /// </summary>
    public static Level Low { get; }    = CreateMember(0);

    /// <summary>   
    /// <c><![CDATA[ (value: 1) ]]></c>
    /// </summary>
    public static Level Medium { get; } = CreateMember(1);

    /// <summary>
    /// <para>Has a value of 2.</para>
    /// <c><![CDATA[ (value: 2) ]]></c> 
    /// </summary>
    public static Level High { get; }   = CreateMember(2);
}


/// <summary>
/// Call this method in order to create discovered enum members while invoking them (on the fly). So enum members are automatically deleted when not being used.
/// </summary>
public static class LevelExtensions
{
    public static Level CreateMember(this Level member, Int32? value = null, string? comment = null) 
        => member;
}

#nullable restore

Discoverability

Enum member discoverability makes it possible to create an enum member the moment you reference it in your IDE. This ensures that enum members that are not used anymore are deleted. The members are automatically generated using C# source generators.

Explicit discoverability

  • Is enabled by adding the DiscoverEnumMembers-attribute on the enum.
  • Use [Enum].[Member].CreateMember() to create a source generated enum-member on the fly.
  • A member value and/or comment can be provided.
Example without arguments

alt text

Example with arguments

alt text

It generates the following code:

// <auto-generated />
#nullable enable

using System;
using System;
using CodeChops.MagicEnums;

namespace CodeChops.MagicEnums.UnitTests.SourceGeneration.AttributeMembers;

/// <summary>
/// <list type="bullet">
/// <item><c><![CDATA[ CreditCard = 7 ]]></c></item>
/// </list>
/// </summary>
public sealed partial record class PaymentMethod
{
    /// <summary>
    /// <para>Only works with VISA.</para>
    /// <c><![CDATA[ (value: 7) ]]></c>
    /// </summary>
    public static PaymentMethod CreditCard { get; } = CreateMember(7);
}

/// <summary>
/// Call this method in order to create discovered enum members while invoking them (on the fly). So enum members are automatically deleted when not being used.
/// </summary>
public static class PaymentMethodExtensions
{
    public static PaymentMethod CreateMember(this PaymentMethod member, Int32? value = null, string? comment = null) 
        => member;
}

#nullable restore

Implicit discoverability

  • Is enabled by adding the DiscoverEnumMembers-attribute with implicit set to true.
  • Use [Enum].[Member] to create a source generated enum-member on the fly.
  • No member value or comment can be provided.
Example

alt text

It generates the following code:

// <auto-generated />
#nullable enable

using System;
using System;
using CodeChops.MagicEnums;

namespace CodeChops.MagicEnums.UnitTests.SourceGeneration.AttributeMembers;

/// <summary>
/// <para><em>Do not rename!</em></para>
/// <list type="bullet">
/// <item><c><![CDATA[ NameIsNullOrWhiteSpace = "NameIsNullOrWhiteSpace" ]]></c></item>
/// </list>
/// </summary>
public sealed partial record class ErrorCode
{
    /// <summary>
    /// <para><em>Do not rename!</em></para>
    /// <para><c><![CDATA[ (value: "NameIsNullOrWhiteSpace") ]]></c></para>
    /// </summary>
    public static ErrorCode NameIsNullOrWhiteSpace { get; } = CreateMember("NameIsNullOrWhiteSpace");
}

/// <summary>
/// Call this method in order to create discovered enum members while invoking them (on the fly). So enum members are automatically deleted when not being used.
/// </summary>
public static class ErrorCodeExtensions
{
    public static ErrorCode CreateMember(this ErrorCode member, String? value = null, string? comment = null) 
        => member;
}

#nullable restore

Optimization

Generally your enum does not dynamically add members at runtime. If this is the case, the attribute DisableConcurrency can be placed on the enum. It disables concurrency and therefore optimises memory usage and speed.

Warning! Only use this attribute when you are sure that no race conditions can take place when creating / reading members.

Product Compatible and additional computed target framework versions.
.NET 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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on CodeChops.MagicEnums:

Package Downloads
CodeChops.ImplementationDiscovery

Provides easy-accessible, design-time and runtime information about implementations throughout your code.

CodeChops.MagicEnums.Json

Json (de)serialization for MagicEnums.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.9.0 497 3/20/2023
3.8.3 370 3/10/2023
3.8.2 369 3/6/2023
3.8.0 360 1/27/2023
3.7.1 354 1/22/2023
3.6.0 733 1/7/2023
3.5.0 370 1/6/2023
3.4.4 460 1/6/2023
3.4.2 332 1/4/2023
3.4.1 405 1/3/2023
3.4.0 344 1/2/2023
3.3.9 300 1/2/2023
3.3.5 353 12/23/2022
3.3.4 309 12/22/2022
3.3.3 293 12/19/2022
3.3.2 331 12/16/2022
3.3.1 336 12/15/2022
3.3.0 311 12/14/2022
2.9.9 543 9/17/2022
2.9.8 536 9/16/2022
2.9.5 545 9/16/2022
2.9.4 431 9/15/2022
2.9.3 459 9/14/2022
1.5.1 551 7/11/2022
1.5.0 721 7/11/2022
1.4.3 602 7/11/2022
1.4.2 1,161 7/10/2022
1.4.1 802 7/8/2022
1.3.10 548 7/8/2022
1.2.9 853 7/6/2022
1.2.8 426 7/6/2022
1.2.7 420 7/5/2022
1.2.6 432 7/5/2022
1.2.5 1,501 6/23/2022
1.2.4 413 6/22/2022
1.2.3 655 6/21/2022
1.2.2 449 6/14/2022
1.2.1 2,072 6/14/2022
1.2.0 1,297 6/13/2022
1.1.1 1,765 3/16/2022
1.1.0 444 3/9/2022
1.0.3 565 3/4/2022
1.0.2 436 3/3/2022
1.0.1 440 3/3/2022
0.9.6 452 2/20/2022

Updated README.md. Source generator now implicitly enumerates. Enhanced value in comments.