Skip to content

Feature Request: Support for EnumMember(Value = ) #73

@ryan-morris

Description

@ryan-morris

Request

Would it be possible to add support for the EnumMember attribute?

[EnumExtensions]
enum UserTypeTest
{
    [Display(Name = "مرد")]
    [EnumMember(Value = "men_value")]
    Men,

    [Display(Name = "زن")]
    [EnumMember(Value = "women_value")]
    Women,

    //[Display(Name = "نامشخص")]
    None
}

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");

        var canParse = UserTypeTestExtensions.TryParse("men_value", out var parsed);

        if (canParse)
        {
            Console.WriteLine($"Parsed men_value as {parsed}");
        }
        else
        {
            Console.WriteLine("Unable to parse using EnumMember attribute");
        }

        // ideally something like this that would return string values: men_value, women_value
        var allValues = UserTypeTestExtensions.GetValues();

        // ideally something like this for fetching the actual Value that is set: men_value or women_value based on actual enum
        // var value = UserTypeTest.Men.ToValueFast();
    }
}

Use Case

For our use case, we have DTO classes that are mapped from JSON and validated

{
   "npc_gender": "men_value",
}
class RequestDto
{
   [JsonPropertyName("npc_gender")]
   public string? NpcGender {get; init;}
}

Validation happens, then it's mapped if valid:

class Request
{
   public required UserTypeTest UserType {get; init;}
}

We use currently have manual mapping to internal types, but ideally custom enum handling in things like mapperly or other cases of using an auto mapper someone could make use of these methods going from RequestDto to Request

VERY Bad implementation

I won't pretend to even have suggestions on how this would be implemented properly or efficiently, but this is a rough version of what I use now:

internal class Program
{

    static void Main(string[] args)
    {
        var emValue = UserTypeTest.Women.ToValueStringFast();
        var canParse = EnumExtensions.TryParse<UserTypeTest>(emValue, out var back);

        Console.WriteLine($"CAN PARSE: {canParse} PARSED: {emValue}; BACK: {back}");

        var canParse2 = EnumExtensions.TryParse<UserTypeTest>("None", out var back2);

        Console.WriteLine($"CAN PARSE: {canParse2} PARSED: BACK2: {back2}");

    }
}

// bad implementations
static class EnumExtensions
{
    public static string ToValueStringFast(this Enum enumVal)
    {
        var type = enumVal.GetType();
        var memInfo = type.GetMember(enumVal.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(EnumMemberAttribute), inherit: false);

        if (attributes.Length == 0)
        {
            throw new Exception($"EnumMember attribute not found for {enumVal?.ToString()} of {enumVal?.GetType()}");
        }

        if (((EnumMemberAttribute)attributes[0]).Value == null)
        {
            throw new Exception($"EnumMember value for {enumVal?.ToString()} of {enumVal?.GetType()} was null. Cannot get as string.");
        }

        return ((EnumMemberAttribute)attributes[0]).Value!;
    }

    public static bool TryParse<T>(string? value, out T? val)
        where T : Enum
    {
        var type = typeof(T);

        foreach (var field in type.GetFields())
        {
            EnumMemberAttribute? attribute = Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)) as EnumMemberAttribute;
            if (attribute != null)
            {
                if (attribute.Value == value)
                {
                    var emValue = field.GetValue(null);
                    if (emValue == null)
                    {
                        throw new Exception($"Enum member for {value} as {typeof(T)} was null");
                    }

                    val = (T)emValue;
                    return true;
                }
            }
        }

        val = default;
        return false;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions