【问题标题】:Why does Enum.Parse create undefined entries?为什么 Enum.Parse 创建未定义的条目?
【发布时间】:2010-08-26 12:50:40
【问题描述】:
class Program
{
    static void Main(string[] args)
    {
        string value = "12345";
        Type enumType = typeof(Fruits);
        Fruits fruit = Fruits.Apple;
        try
        {
            fruit = (Fruits) Enum.Parse(enumType, value);
        }
        catch (ArgumentException)
        {
            Console.WriteLine(String.Format("{0} is no healthy food.", value));
        }
        Console.WriteLine(String.Format("You should eat at least one {0} per day.", fruit));
        Console.ReadKey();
    }

    public enum Fruits
    {
        Apple,
        Banana,
        Orange
    }
}

如果执行上面的代码,结果显示:

你应该每天至少吃一个 12345。

我真的希望在传递未知名称(字符串)时抛出 ArgumentException。仔细查看 Enum.Parse 定义会发现:

总结:
将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象。

异常:
ArgumentException:enumType 不是 Enum。 - 或 - value 为空字符串或仅包含空格。 - 或 - value 是一个名称,但不是为枚举定义的命名常量之一

即如果传递了整数的字符串表示形式,则会创建一个新的枚举值,现在设计会抛出异常。这有意义吗?

至少我现在知道在Enum.Parse()之前打电话给Enum.IsDefined(enumType, value)

【问题讨论】:

  • 你为什么要问和回答你自己的问题?
  • 问题是关于行为...
  • 希望我已经以一种有意义的方式解释了我的回答中的行为(或我对它的解释无论如何)。如果它没有让我知道,我会看看我能做些什么来解决这个问题=)

标签: c# .net enums behavior


【解决方案1】:

“命名常量”是枚举值的文本表示,而不是您分配给它的数字。

如果你改变:

string value = "12345";

收件人:

string value = "Cake";

您会看到预期的错误,因为“值是一个名称,但不是为枚举定义的命名常量之一。”。在这种情况下,您传入的值 一个名称“Cake”,但不是枚举中的一个。

考虑Enum.Parse(enumType, value); 执行以下操作:

  1. 如果 value 是空引用,则抛出 ArgumentNullException
  2. value 中的值是enumType 枚举中的命名常量之一。如果是,则从枚举中返回该值并停止。
  3. value 中的值是否可直接转换为基础类型(在本例中为 Int32),如果是,则返回该值并停止(即使该值没有命名常量)。
  4. value 中的值是否可以直接转换为基础类型,但超出了基础类型的范围?例如该值是一个字符串,其中包含一个大于 MAXINT 的数字。如果是,请发送OverflowException
  5. 值不能转换为基础类型吗?如果是,则抛出 ArgumentException。

【讨论】:

  • 你是对的,因为 Enum 是一个 Int32 Enum.Parse("") 返回一个有效值是正确的。我的问题是由于将 Enum 视为受限的 Int32 而实际上并非如此。
  • 严格来说,这并不正确。字符串永远不能“转换”为 int,它需要通过多种方法(Convert.ToInt32int.Parseint.TryParse 等)进行转换。 3.) 的更准确表达式是“value 中的值是否直接可转换 为基础类型(在本例中为Int32)?如果是,则返回该值并停止(即使该值没有命名常量)。
【解决方案2】:

枚举可以是其基本整数类型的任何值。它不仅限于命名常量。

例如,以下是完全有效的:

enum Foo{
    A,
    B,
    C,
    D
}

Foo x = (Foo)5;

即使 5 不对应于命名常量,它仍然是 Foo 的有效值,因为 Foo 的基础类型是 Int32

如果调用x.ToString(),返回值将简单地为“5”,因为没有命名常量与x 的值相对应。

Enum.Parse()Enum.ToString() 的逆函数。您应该期望Enum.ToString() 可以返回Enum.Parse() 可以接受的任何内容。例如,这包括标志枚举的逗号分隔值:

[Flags]
enum Foo{
    A = 1,
    B = 2,
    C = 4,
    D = 8
}

Foo x = Foo.A | Foo.B | Foo.C | Foo.D;
int i = (int)x;
string s = x.ToString();
Console.WriteLine(i);
Console.WriteLine(s);
Console.WriteLine((Foo)Enum.Parse(typeof(Foo), i.ToString()) == x);
Console.WriteLine((Foo)Enum.Parse(typeof(Foo), s) == x);

输出:

15 A B C D 真的 真的

编辑:

你真正想要的是这样的:

static Enum GetEnumValue(Type enumType, string name){
    // null-checking omitted for brevity

    int index = Array.IndexOf(Enum.GetNames(enumType), name);
    if(index < 0)
        throw new ArgumentException("\"" + name + "\" is not a value in " + enumType, "name");

    return Enum.GetValues(enumType).GetValue(index);
}

或不区分大小写的版本:

static Enum GetEnumValue(Type enumType, string name, bool ignoreCase){
    // null-checking omitted

    int index;
    if(ignoreCase)
        index = Array.FindIndex(Enum.GetNames(enumType),
            s => string.Compare(s, name, StringComparison.OrdinalIgnoreCase) == 0);
            // or StringComparison.CurrentCultureIgnoreCase or something if you
            // need to support fancy Unicode names
    else index = Array.IndexOf(Enum.GetNames(enumType), name);

    if(index < 0)
        throw new ArgumentException("\"" + name + "\" is not a value in " + enumType, "name");

    return Enum.GetValues(enumType).GetValue(index);
}

【讨论】:

  • 感谢您对Enum.Parse()ToString() 的额外见解,我不知道逗号分隔值的功能。
【解决方案3】:

你需要使用 Enum.IsDefined:

http://msdn.microsoft.com/en-us/library/essfb559.aspx

using System;

    [Flags] enum Colors { None=0, Red = 1, Green = 2, Blue = 4 };

    public class Example
    {
       public static void Main()
       {
          string[] colorStrings = { "0", "2", "8", "blue", "Blue", "Yellow", "Red, Green" };
          foreach (string colorString in colorStrings)
          {
             try {
                Colors colorValue = (Colors) Enum.Parse(typeof(Colors), colorString);        
                if (Enum.IsDefined(typeof(Colors), colorValue) | colorValue.ToString().Contains(","))  
                   Console.WriteLine("Converted '{0}' to {1}.", colorString, colorValue.ToString());
                else
                   Console.WriteLine("{0} is not an underlying value of the Colors enumeration.", colorString);
             }
             catch (ArgumentException) {
                Console.WriteLine("'{0}' is not a member of the Colors enumeration.", colorString);
             }
          }
       }
    }
    // The example displays the following output:
    //       Converted '0' to None.
    //       Converted '2' to Green.
    //       8 is not an underlying value of the Colors enumeration.
    //       'blue' is not a member of the Colors enumeration.
    //       Converted 'Blue' to Blue.
    //       'Yellow' is not a member of the Colors enumeration.
    //       Converted 'Red, Green' to Red, Green.

【讨论】:

  • 是的,我知道,但我不明白为什么 Enum.Parse 会生成新值,这就像调用 Int.Parse("big number not known to human") 并得到结果...
【解决方案4】:

我个人认为Enum.Parse接受数字的字符串表示是一个遗憾。如果您正在寻找替代方案,您可能想查看我的 Unconstrained Melody 项目,它具有各种解析选项,并且也是强类型的。

您当然可以Enum.IsDefined 与解析结合使用。你确定想要接受数字的字符串版本吗?还是你真的只期待名字?

【讨论】:

  • Enum.Parse接受数字的字符串表示真的很可惜吗?如果没有,我会认为它已损坏,因为这意味着枚举值可能不会往返于字符串,即Enum.Parse(enumValue.ToString()) 可能会失败。
猜你喜欢
  • 1970-01-01
  • 2021-04-17
  • 2021-08-19
  • 1970-01-01
  • 1970-01-01
  • 2021-12-10
  • 1970-01-01
  • 2012-02-25
  • 1970-01-01
相关资源
最近更新 更多