关于c#:Custom Enum Parse

Custom Enum Parse

我有一个枚举如下:

1
public enum MyEnum {  One,  Two,  Three}

我想将一些字符串削减到上面的枚举,例如,下面的字符串将被解析为myenum。

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

我知道我可以维护一个映射函数来完成这项工作。但是,我只想找到一种更好的方法,例如重写enum.parse函数或类似的方法。我试图使用IConvertable,但似乎不可能。有什么想法吗?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
[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));
        }
    }
}

然后可以(注意[Name]、多个[Name]或多个名称的单个[Name]的双语法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}

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

注意使用内部类(ParseEnumImpl来缓存TEnum的"名称"。


最好的方法就是存储一个带有映射的Dictionary

1
2
3
4
5
6
static Dictionary<string, string> _mappings = new Dictionary<string, string>
{
    {"Two","Two" },
    {"Second","Two" },
    {"2","Two" }
};

然后您将其称为Enum.Parse(Type, String, Boolean)的case-insetive版本:

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

通常,我更喜欢简单的解决方案,因为它们比"重写Enum.Parse函数或类似的功能"更容易理解。

但我们可以通过使用Dictionary更简单地做到这一点:

1
2
3
4
5
6
static Dictionary<string, MyEnum> _mappings = new Dictionary<string, MyEnum>
{
    {"Two", MyEnum.Two },
    {"Second", MyEnum.Two },
    {"2", MyEnum.Two }
};

现在要获取枚举:

1
MyEnum myEnumb = _mappings[str];

后一种方法也提高了性能,因为我们避免了Enum.Parse调用。


您试图分析两种不同的情况:

  • 输入包含枚举的名称
  • 输入包含枚举的值
  • 如果这两种情况是输入中唯一的情况,您可以简单地使用Enum.Tryparse方法(String、Boolean、Tenum)尝试以不区分大小写的方式分析文本:

    1
    2
    3
    4
    5
        MyEnum output;
        if (Enum.TryParseEnum(input,true,out output))
        {
            // Process succesfull value
        }

    从文档示例中,您可以看到TryParse可以处理文本和数字字符串输入。

    至于解析Second,本文与枚举没有关系,只是在编码人员的头脑中。在这种情况下,您真的需要创建一个映射并将其放置在某个地方——一个字典、一个自定义属性等。

    事实上,如果数据来自外部文件,这是ETL问题,而不是解析问题。在这种情况下,典型的解决方案是创建查找表,将输入映射到可识别的输出,并在解析之前用查找值替换输入。