【问题标题】:Custom Enum Parse自定义枚举解析
【发布时间】:2015-08-12 03:41:09
【问题描述】:

我有一个枚举如下:

public enum MyEnum {  One,  Two,  Three}

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

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

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

【问题讨论】:

  • 我知道我可以维护一个映射函数来完成这项工作。 这个 :-)
  • 注意有一长串类似的问题:stackoverflow.com/questions/4367723/…
  • 您可以使用描述属性,如本问题stackoverflow.com/questions/2650080/…中所述
  • “Second”和MyEnum.Two之间没有任何关系,所以看看链接的问题。
  • 伙计们,这不是重复的。关闭前请仔细阅读。

标签: c# enums


【解决方案1】:
[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]):

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
}

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

注意使用内部类 (ParseEnumImpl&lt;TEnum&gt;) 来缓存 TEnum“名称”。

【讨论】:

    【解决方案2】:

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

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

    然后你调用Enum.Parse(Type, String, Boolean)的不区分大小写版本:

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

    通常,我更喜欢简单的解决方案,因为它们比“覆盖Enum.Parse 函数或类似的东西”更容易理解。

    但我们可以通过使用Dictionary&lt;string, MyEnum&gt; 来更简单:

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

    现在获取您的枚举:

    MyEnum myEnumb = _mappings[str];
    

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

    【讨论】:

      【解决方案3】:

      您正在尝试解析两种不同的情况:

      1. 输入包含枚举的名称
      2. 输入包含枚举的值

      如果输入中只有这两种情况,您可以简单地使用Enum.TryParse Method (String, Boolean, TEnum) 尝试以不区分大小写的方式解析文本:

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

      C# 7+ 更新:现在可以在同一行中声明变量:

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

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

      至于解析Second,这段文字与枚举无关,除非在编码员的脑海中。在这种情况下,您确实必须创建一个映射并将其放置在某处 - 一个字典、一个自定义属性等。

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

      【讨论】:

        【解决方案4】:

        您可以使用 nuget 包 Enums.NET 对描述属性进行反向查找。

        注意有一个带有 ignoreCase 布尔值的重载。

        public enum PhoneCode
        {
            [DescriptionAttribute("991")]
            Emergency,
            [DescriptionAttribute("411")]
            Info,
        }
        
        PhoneCode code;
        EnumsNET.Enums.TryParse("991", out code, EnumsNET.EnumFormat.Description)
        

        【讨论】:

          【解决方案5】:

          虽然我确实喜欢这些属性,但我自己使用了一个映射,而是像这样扩展了 string 基本类型:

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

          然后我有一个 static 类用于我的扩展...其中包含以下代码:

          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;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-09-11
            • 2020-04-25
            • 2021-07-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多