Skip to main content

jos.enumeration by Josef Ottosson

NuGet / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: jos.enumeration

Package Description

Author: Josef Ottosson

NuGet: https://www.nuget.org/packages/jos.enumeration/

You can find more details at https://github.com/joseftw/jos.enumeration

Source: https://github.com/joseftw/jos.enumeration

Original Readme

note

JOS.Enumeration

Enumeration implementation with source generation support.

Installation

JOS.Enumeration

Contains the IEnumeration interface and a System.Text.Json JsonConverter. The JOS.Enumeration.SourceGenerator package contains the actual source generator.

Don't forget to install that one as well. 😃

dotnet add package JOS.Enumeration
dotnet add package JOS.Enumeration.SourceGenerator

JOS.Enumeration.Database.Dapper

Contains a custom TypeHandler to use with Dapper.

dotnet add package JOS.Enumeration.Database.Dapper

JOS.Enumeration.Database.EntityFrameworkCore

Contains ConfigureEnumeration extension method to allow usage with EntityFramework Core.

dotnet add package JOS.Enumeration.Database.EntityFrameworkCore

Usage

  • Create a new partial record or class
  • Implement the IEnumeration<T> interface
  • Add your Enumeration items
public partial record Hamburger : IEnumeration<Hamburger>
{
public static readonly Hamburger Cheeseburger = new (1, "Cheeseburger");
public static readonly Hamburger BigMac = new(2, "Big Mac");
public static readonly Hamburger BigTasty = new(3, "Big Tasty");
}

The source generator will implement the following interface:

// Default implementation -> int as Value
public interface IEnumeration<T> : IEnumeration<int, T> where T : IEnumeration<T>
{
}

public interface IEnumeration<TValue, TType> where TValue : IConvertible
{
TValue Value { get; }
string Description { get; }
static abstract IReadOnlySet<TType> GetAll();
static abstract IEnumerable<TType> GetEnumerable();
static abstract TType FromValue(TValue value);
static abstract TType FromDescription(string description);
static abstract TType FromDescription(ReadOnlySpan<char> description);
static abstract Type ValueType { get; }
}

The following code will be generated:

[System.Diagnostics.DebuggerDisplay("{Description}")]
[System.CodeDom.Compiler.GeneratedCode("JOS.Enumeration.SourceGenerator", "4.1.11-beta+afeaa87a52")]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public partial record Hamburger : IComparable<JOS.Enumerations.Hamburger>
{
private static readonly IReadOnlySet<JOS.Enumerations.Hamburger> AllItems;
static Hamburger()
{
AllItems = new HashSet<JOS.Enumerations.Hamburger>(3)
{
Cheeseburger,
BigMac,
BigTasty,
}.ToFrozenSet();
}

private Hamburger(int value, string description)
{
Value = value;
Description = description ?? throw new ArgumentNullException(nameof(description));
}

public int Value { get; }
public string Description { get; }

public static IReadOnlySet<JOS.Enumerations.Hamburger> GetAll()
{
return AllItems;
}

public static IEnumerable<JOS.Enumerations.Hamburger> GetEnumerable()
{
yield return Cheeseburger;
yield return BigMac;
yield return BigTasty;
}

public static JOS.Enumerations.Hamburger FromValue(int value)
{
return value switch
{
1 => Cheeseburger,
2 => BigMac,
3 => BigTasty,
_ => throw new InvalidOperationException($"'{value}' is not a valid value in 'JOS.Enumerations.Hamburger'")};
}

public static JOS.Enumerations.Hamburger FromDescription(string description)
{
return description switch
{
"Cheeseburger" => Cheeseburger,
"Big Mac" => BigMac,
"Big Tasty" => BigTasty,
_ => throw new InvalidOperationException($"'{description}' is not a valid description in 'JOS.Enumerations.Hamburger'")};
}

public static JOS.Enumerations.Hamburger FromDescription(ReadOnlySpan<char> description)
{
return description switch
{
"Cheeseburger" => Cheeseburger,
"Big Mac" => BigMac,
"Big Tasty" => BigTasty,
_ => throw new InvalidOperationException($"'{description}' is not a valid description in 'JOS.Enumerations.Hamburger'")};
}

public static Type ValueType => typeof(int);

public int CompareTo(JOS.Enumerations.Hamburger? other) => Value.CompareTo(other!.Value);
public static implicit operator int (JOS.Enumerations.Hamburger item) => item.Value;
public static implicit operator JOS.Enumerations.Hamburger(int value) => FromValue(value);
}

Features

  • Generic value
  • Generated IComparable<T> method.
  • Generated implicit operators (convert to/from int).
  • Generated optimized GetAll, FromValue and FromDescription methods.
  • System.Text.Json support
  • Database support (Dapper and EF Core).

Generic value

It's possible to use a generic value instead of the default int value by implementing the IEnumeration<TValue, TEnumeration> interface.

public partial record Car : IEnumeration<string, Car>
{
public static readonly Car FerrariSpider = new("ferrari-spider", "Ferrari Spider");
public static readonly Car TeslaModelY = new("tesla-model-y", "Tesla Model Y");
}

TValue has a IConvertible constraint.

The following types has been tested and are guaranteed to work:

  • int (default)
  • bool
  • decimal
  • long
  • string
  • uint
  • ulong

JSON

The package comes with a JsonConverterFactory. Example:

var jsonSerializerOptions = new JsonSerializerOptions
{
Converters = { new EnumerationJsonConverterFactory() }
};

It supports the following scenarios:

  • Serializing to TValue
  • Deserializing from TValue

If you want any other behaviour, just create your own converter and register it.

Database

public class MyEntity
{
public MyEntity(Guid id, Hamburger hamburger)
{
Id = id;
Hamburger = hamburger;
}

public Guid Id { get; }
public Hamburger Hamburger { get; }
}

Dapper

  • Register the TypeHandler: SqlMapper.AddTypeHandler(new EnumerationTypeHandler<Hamburger>())
  • Query like this:
var results = (await actConnection.QueryAsync<MyEntity>(
"SELECT id, hamburger from my_entities WHERE id = @id", new {id = myEntity.Id})).ToList();

EF Core

  • Configure your DB Context
public DbSet<MyEntity> MyEntities { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(JosEnumerationDbContext).Assembly);
}
public class MyEntityEntityTypeConfiguration : IEntityTypeConfiguration<MyEntity>
{
public void Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.Hamburger).ConfigureEnumeration().IsRequired();
}
}
  • Query:
var result = await myDbContext.MyEntities.FirstAsync(x => x.Id == myEntity.Id); 

Primitive Collections

Support for primitive collections in net8.0 can be configured like this:

EF Core

public void Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.ConfigureEnumeration<MyEntity, string, Car>(x => x.Cars);
}

Dapper

SqlMapper.AddTypeHandler(new EnumerationArrayTypeHandler<string, Car>());

About

note

Generating enum from static consts

How to use

Example (source csproj, source files)

This is the CSharp Project that references jos.enumeration

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JOS.Enumeration" Version="4.0.2" />
<PackageReference Include="JOS.Enumeration.SourceGenerator" Version="4.0.2" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

// <auto-generated>
// This code was auto generated by JOS.Enumeration.SourceGenerator
// </auto-generated>
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
#if NET8_0_OR_GREATER
using System.Collections.Frozen;
#endif
using JOS.Enumeration;

namespace EnumDemo;
[System.Diagnostics.DebuggerDisplay("{Description}")]
[System.CodeDom.Compiler.GeneratedCode("JOS.Enumeration.SourceGenerator", null)]
partial record CarTypes : IComparable<EnumDemo.CarTypes>
{
private static readonly IReadOnlySet<EnumDemo.CarTypes> AllItems;
static CarTypes()
{
AllItems = new HashSet<EnumDemo.CarTypes>(4)
{
Dacia,
Tesla,
BMW,
Mercedes,
}.ToFrozenSet();
}

private CarTypes(int value, string description)
{
Value = value;
Description = description ?? throw new ArgumentNullException(nameof(description));
}

public int Value { get; }
public string Description { get; }

public static IReadOnlySet<EnumDemo.CarTypes> GetAll()
{
return AllItems;
}

public static IEnumerable<EnumDemo.CarTypes> GetEnumerable()
{
yield return Dacia;
yield return Tesla;
yield return BMW;
yield return Mercedes;
}

public static EnumDemo.CarTypes FromValue(int value)
{
return value switch
{
1 => Dacia,
2 => Tesla,
3 => BMW,
4 => Mercedes,
_ => throw new InvalidOperationException($"'{value}' is not a valid value in 'EnumDemo.CarTypes'")};
}

public static EnumDemo.CarTypes FromDescription(string description)
{
return description switch
{
"Dacia" => Dacia,
"Tesla" => Tesla,
"BMW" => BMW,
"Mercedes" => Mercedes,
_ => throw new InvalidOperationException($"'{description}' is not a valid description in 'EnumDemo.CarTypes'")};
}

public static EnumDemo.CarTypes FromDescription(ReadOnlySpan<char> description)
{
return description switch
{
"Dacia" => Dacia,
"Tesla" => Tesla,
"BMW" => BMW,
"Mercedes" => Mercedes,
_ => throw new InvalidOperationException($"'{description}' is not a valid description in 'EnumDemo.CarTypes'")};
}

public static Type ValueType => typeof(int);

public int CompareTo(EnumDemo.CarTypes? other) => Value.CompareTo(other!.Value);
public static implicit operator int (EnumDemo.CarTypes item) => item.Value;
public static implicit operator EnumDemo.CarTypes(int value) => FromValue(value);
}

Useful

Download Example (.NET C#)

Share jos.enumeration

https://ignatandrei.github.io/RSCG_Examples/v2/docs/jos.enumeration

In the same category (Enum) - 6 other generators

CredFetoEnum

EnumClass

EnumUtilities

FusionReactor

NetEscapades.EnumGenerators

PMart.Enumeration