Reputation: 327
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
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
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
Reputation: 131180
You are trying to parse two distinct cases:
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
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
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
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