Skip to content

feeleen/StringEnum.Net

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

67 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build status NuGet Badge

StringEnum.NET - Enumeration-like string values

C# .Net

Supported Versions

  • .NET 8.0, 9.0, 10.0
  • Newtonsoft.Json 13.0.4+
  • System.Text.Json 8.0.5+

Quick Start

Define your StringEnum clean and simple:

public class MyPet : StringEnum<MyPet>
{
    public static MyPet Monkey = New(); // = "Monkey"
    public static MyPet Cat = New();
    public static MyPet Rabbit = New();
}

More initialization options:

public class MyPet : StringEnum<MyPet>
{
    public static MyPet Rabbit = New("Rabbits");
    public static MyPet Dog = New(EnumCase.Upper, "Dog"); // MyPet.Dog.ToString() -> "DOG"
    public static MyPet Ghost = New(null);  // handy when values in dataobject may have null values
    public static MyPet Empty = New(string.Empty);
}

Add additional properties for your StringEnum:

public class MyFlower : StringEnum<MyFlower>
{
    public static MyFlower Rose = New().HasPropertyValue(x => x.AdditionalInfo, "Big");

    public static MyFlower Camomile = New()
        .HasPropertyValue(x => x.AdditionalInfo, "Small")
        .HasPropertyValue(x => x.DefaultQuantity, 15);

    // your custom properties here:
    public string AdditionalInfo { get; protected set; }
    public int? DefaultQuantity { get; protected set; }
}

Usage Examples

Basic Operations

var mouse = MyPet.Mouse;

if (mouse == "Mouse") // compare with string without .ToString()
{
    return true;
}

if (mouse == MyPet.Parse("mouse")) // parsing is case insensitive
{
    return true;
}

// implicit conversions example
string cat = MyPet.Cat;
Assert.IsTrue(cat == "Cat");

string dog = (MyPet)"Dog";
Assert.IsTrue(dog == "Dog");

TryParse (Safe Parsing)

// Returns true if parsing succeeds, false otherwise
if (MyPet.TryParse("Cat", out var pet))
{
    Console.WriteLine($"Parsed: {pet}");
}

// Handle null values
if (MyPet.TryParse(null, out var ghostPet))
{
    // ghostPet will be the null-valued enum member if one exists
}

Comparison Operations (IComparable)

// StringEnum supports comparison operators
if (MyPet.Cat < MyPet.Dog)
{
    Console.WriteLine("Cat comes before Dog alphabetically");
}

// CompareTo method
int result = MyPet.Cat.CompareTo(MyPet.Dog); // negative value
result = MyPet.Cat.CompareTo(MyPet.Cat);     // 0
result = MyPet.Dog.CompareTo(MyPet.Cat);     // positive value

Caching

// AsList() and Parse() use internal caching for performance
var allPets = MyPet.AsList(); // cached after first call

// Multiple Parse calls use cache
var cat1 = MyPet.Parse("Cat"); // first call initializes cache
var cat2 = MyPet.Parse("Cat"); // retrieved from cache

JSON Serialization

Newtonsoft.Json

[JsonConverter(typeof(JsonStringEnumConverter<MyFlower>))]
public class MyFlower : StringEnum<MyFlower>
{
    public static MyFlower Rose = New();
    public static MyFlower Hibiscus = New();
}

public class GardenFlower
{
    public MyFlower FlowerType { get; set; }
    public int Quantity { get; set; }
}

string jsonData = "{'FlowerType' : 'Rose', 'Quantity' : 2 }";
var obj = JsonConvert.DeserializeObject<GardenFlower>(jsonData);

Assert.IsTrue(obj.FlowerType == "Rose");
Assert.IsTrue(obj.FlowerType == MyFlower.Rose);

System.Text.Json (.NET 8+)

using System.Text.Json.Serialization;

[JsonConverter(typeof(SystemTextJsonStringEnumConverter<MyFlower>))]
public class MyFlower : StringEnum<MyFlower>
{
    public static MyFlower Rose = New();
    public static MyFlower Hibiscus = New();
}

public class GardenFlower
{
    public MyFlower FlowerType { get; set; }
    public int Quantity { get; set; }
}

string jsonData = """{"FlowerType" : "Rose", "Quantity" : 2 }""";
var obj = JsonSerializer.Deserialize<GardenFlower>(jsonData);

Assert.IsTrue(obj.FlowerType == "Rose");
Assert.IsTrue(obj.FlowerType == MyFlower.Rose);

TypeConverter and IConvertible

[TypeConverter(typeof(StringEnumConverter<MyFlower>))] // default type converter
public class MyFlower : StringEnum<MyFlower>
{
    public static MyFlower Rose = New();
    public static MyFlower Hibiscus = New();
}

// TypeConverter usage
TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(MyFlower));
var rose = (MyFlower)typeConverter.ConvertFrom(null, Thread.CurrentThread.CurrentCulture, "Rose");

// IConvertible usage
var fl = MyFlower.Hibiscus;
var res = Convert.ChangeType(fl, typeof(string));
Assert.IsTrue((string)res == "Hibiscus");

// GetTypeCode returns TypeCode.String
Assert.AreEqual(TypeCode.String, fl.GetTypeCode());

Use with Data Entities

Linq2DB (v5+)

  1. Define your model:
public class PersonType : StringEnum<PersonType>
{
    public static PersonType EM = New();
    public static PersonType SP = New();
    public static PersonType SC = New();
    public static PersonType VC = New();
    public static PersonType IN = New();
    public static PersonType GC = New();
}

[Table(Name = "[Person].[Person]")]
public class Person
{
    [PrimaryKey, Identity]
    public int BusinessEntityID { get; set; }
    
    [Column]
    public PersonType PersonType { get; set; } // string values "EM", "SP", "SC" ... etc.
}
  1. Setup type conversion via MappingSchema:
using LinqToDB;
using LinqToDB.Mapping;

// Create mapping schema with converters
var ms = new MappingSchema();

// Register converters for PersonType
ms.SetConverter<PersonType, string>(v => v.Value);
ms.SetConverter<string, PersonType>(s => s == null ? null : PersonType.Parse(s));

// Apply to your data connection
var options = new DataOptions()
    .UseSqlServer(connectionString)
    .UseMappingSchema(ms);

using var db = new DataConnection(options);

Or using FluentMappingBuilder:

var ms = new MappingSchema();
var builder = new FluentMappingBuilder(ms);

builder.Entity<Person>()
    .Property(e => e.PersonType)
    .HasConversion(
        v => v.Value,
        s => s == null ? null : PersonType.Parse(s));

var options = new DataOptions()
    .UseSqlServer(connectionString)
    .UseMappingSchema(ms);
  1. Usage:
// select records
var persons = await db.GetTable<Person>().ToListAsync();

// update record (use .Value for parameters)
var pers = new Person() { PersonType = PersonType.VC, BusinessEntityID = 1675 };
await db.GetTable<Person>()
    .Where(x => x.BusinessEntityID == pers.BusinessEntityID)
    .Set(x => x.PersonType, pers.PersonType.Value)
    .UpdateAsync();

EF Core

[Keyless]
public class PersonTitle : StringEnum<PersonTitle>
{
    public static PersonTitle Mr = New("Mr.");
    public static PersonTitle Ms = New("Ms.");
    public static PersonTitle Mss = New("Ms");
    public static PersonTitle Mrs = New("Mrs.");
    public static PersonTitle Sr = New("Sr.");
    public static PersonTitle Sra = New("Sra.");
    public static PersonTitle Undefined = New(null);
}

// setup converter in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Person>()
        .Property(e => e.Title)
        .HasConversion(
            v => v.Value,
            v => v == null ? null : PersonTitle.Parse(v));

    base.OnModelCreating(modelBuilder);
}

Features Summary

Feature Description
Strong typing Type-safe string enums with compile-time checking
Case-insensitive parsing Parse() and TryParse() ignore case
Null support New(null) creates null-valued members
Custom properties HasPropertyValue() for additional data
JSON serialization Newtonsoft.Json and System.Text.Json support
IComparable Comparison operators and CompareTo()
IConvertible Convert to/from other types
TypeConverter Design-time and runtime conversion
Caching AsList() and Parse() use internal caching
ORM integration Linq2DB and EF Core support

Installation

dotnet add package StringEnum.Net

License

MIT License - see LICENSE.md for details

About

Enumeration-like string values

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages