jones
jones

Reputation: 327

Custom Enum Parse

I have an enum as below:

public enum MyEnum {  One,  Two,  Three}

And I would like to pare some strings to above enum, for example, below strings are going to be parsed as MyEnum.Two:

"Two", "TWO", "Second", "2"

I know I can maintain a mapping function to do this job. However, I just want to find a better way, for example, override Enum.Parse function, or something like that. I have tried to use IConvertable, but it seems not possible. Any idea?

Upvotes: 4

Views: 7831

Answers (6)

David Con
David Con

Reputation: 451

Following @xanatos solution. To obtain the EnumValue you can do next:

public static string GetStringValue(Enum value)
{
  string output = null;
  Type type = value.GetType();
  System.Reflection.FieldInfo fi = type.GetField(value.ToString());
  NameAttribute[] attrs = fi.GetCustomAttributes(typeof(NameAttribute), false) as NameAttribute[];
  if (attrs.Length > 0)
  {
     output = attrs[0].Name[0];
  }
  return output;
}

Upvotes: 0

MaxOvrdrv
MaxOvrdrv

Reputation: 1916

While I do like the attributes, I went with a mapping myself and instead, I extended the string base type like this:

public enum Rating
{
    High,
    Medium,
    Low,
    Other
};

I then have a static class for my extensions... which holds this code in it:

public static Dictionary<string, Rating> RatingsMap = new Dictionary<string, Rating>()
{
    {"Highly recommended", Rating.High},
    {"Ok", Rating.Medium},
    {"Liked", Rating.Medium},
    {"Thumbs down", Rating.Low}
};

public static Rating ToRating(this string me)
{
    Rating retval = Rating.Other;

    if (EXTENSIONS.RatingsMap.ContainsKey(me))
        retval = EXTENSIONS.RatingsMap[me];

    return retval;
}

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131180

You are trying to parse two distinct cases:

  1. The input contains the enum's name
  2. The input contains the enum's value

If these two are the only cases in the input, you can simply use the Enum.TryParse Method (String, Boolean, TEnum) to try parsing the text in a case-insensitive manner:

MyEnum output;
if (Enum.TryParse(input,true,out output))
{
    // Process succesful value
}

C# 7+ Update: The variable can be declared in the same line now:

if (Enum.TryParse(input,true,out var output))
{
    // Process succesful value
}

From the documentation examples you see that TryParse can handle both textual and numeric string inputs.

As for parsing Second, this text has no relation to the enum except in the coder's mind. In this case you would really have to create a mapping and place it somewhere - a dictionary, a custom attribute etc.

In fact, if the data comes from external files, this is an ETL problem instead of a parsing problem. In such cases, the typical solution is to create lookup tables that map inputs to recognized outputs and replace the input with the lookup values before parsing

Upvotes: 1

AaronLS
AaronLS

Reputation: 38367

You can use the nuget package Enums.NET to do a reverse lookup on Description Attribute.

Note there's an overload with an ignoreCase boolean.

public enum PhoneCode
{
    [DescriptionAttribute("991")]
    Emergency,
    [DescriptionAttribute("411")]
    Info,
}

PhoneCode code;
EnumsNET.Enums.TryParse("991", out code, EnumsNET.EnumFormat.Description)

Upvotes: 0

Alex Sikilinda
Alex Sikilinda

Reputation: 3013

The best way is just store a Dictionary with mappings:

static Dictionary<string, string> _mappings = new Dictionary<string, string>
{
    { "Two", "Two" },
    { "Second", "Two" },
    { "2", "Two" }
};

Then you call case insensetive version of Enum.Parse(Type, String, Boolean):

String str = "2";
MyEnum number = (MyEnum)Enum.Parse(typeof(MyEnum), _mappings[str], true);

Usually, I prefer simple solution since they are much more understandable than"override Enum.Parse function, or something like that".

But we can do it even more simple by using Dictionary<string, MyEnum>:

static Dictionary<string, MyEnum> _mappings = new Dictionary<string, MyEnum>
{
    { "Two", MyEnum.Two },
    { "Second", MyEnum.Two },
    { "2", MyEnum.Two }
};

Now to get your enum:

MyEnum myEnumb = _mappings[str];

The latter approach also improves performance, since we avoid Enum.Parse invocation.

Upvotes: 4

xanatos
xanatos

Reputation: 111810

[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class NameAttribute : Attribute
{
    public readonly string[] Names;

    public NameAttribute(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException();
        }

        Names = new[] { name };
    }

    public NameAttribute(params string[] names)
    {
        if (names == null || names.Any(x => x == null))
        {
            throw new ArgumentNullException();
        }

        Names = names;
    }
}

public static class ParseEnum
{
    public static TEnum Parse<TEnum>(string value) where TEnum : struct
    {
        return ParseEnumImpl<TEnum>.Values[value];
    }

    public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct
    {
        return ParseEnumImpl<TEnum>.Values.TryGetValue(value, out result);
    }

    private static class ParseEnumImpl<TEnum> where TEnum : struct
    {
        public static readonly Dictionary<string, TEnum> Values = new Dictionary<string,TEnum>();

        static ParseEnumImpl()
        {
            var nameAttributes = typeof(TEnum)
                .GetFields()
                .Select(x => new 
                { 
                    Value = x, 
                    Names = x.GetCustomAttributes(typeof(NameAttribute), false)
                        .Cast<NameAttribute>() 
                });

            var degrouped = nameAttributes.SelectMany(
                x => x.Names.SelectMany(y => y.Names), 
                (x, y) => new { Value = x.Value, Name = y });

            Values = degrouped.ToDictionary(
                x => x.Name, 
                x => (TEnum)x.Value.GetValue(null));
        }
    }
}

Then you can (note the double syntax for [Name], multiple [Name] or single [Name] with multiple names):

public enum TestEnum
{
    [Name("1")]
    [Name("Foo")]
    [Name("F")]
    [Name("XF", "YF")]
    Foo = 1,

    [Name("2")]
    [Name("Bar")]
    [Name("B")]
    [Name("XB", "YB")]
    Bar = 2
}

and

TestEnum r1 = ParseEnum.Parse<TestEnum>("XF");
TestEnum r2;
bool r3 = ParseEnum.TryParse<TestEnum>("YB", out r2);

Note the use of an inner class (ParseEnumImpl<TEnum>) to cache the TEnum "names".

Upvotes: 6

Related Questions