【问题标题】:Cannot understand the behavior of Enums无法理解枚举的行为
【发布时间】:2014-09-08 05:28:29
【问题描述】:

我在 C# 中练习枚举,但我无法理解这些的输出

void Main()
{
    MyEnum a = MyEnum.Top;
    Console.WriteLine(a);
}

对于这个测试,我的枚举和输出是

enum MyEnum
{
    Left, Right, Top, Bottom // Top
}

enum MyEnum
{
    Left, Right, Top = 0, Bottom // Left
}

我认为在朗姆酒时间程序选择第一个值为 0 的项目,为了确认这一点,我将值 0 分配给底部

enum MyEnum
{
    Left, Right, Top = 0, Bottom = 0 // Bottom
}

然后我想也许程序选择了值为 0 的第一项但按字母顺序搜索 所以我将 Top 改为 ATop 并更改了测试用例

void Main()
{
    MyEnum a = MyEnum.ATop;
    Console.WriteLine(a);
}

enum MyEnum
{
    Left, Right, ATop = 0, Bottom = 0 // Bottom
}

enum MyEnum
{
    Left, Right, ATop = 0, Bottom // Left
}

我知道没有人以这种方式使用枚举,但我想知道枚举的这种特殊行为。

【问题讨论】:

  • 但是为什么当我将相同的值分配给 3 个枚举时输出会发生变化(我假设左侧自动分配 0,我明确分配 2)
  • 好吧,通过拥有多个具有相同枚举值的枚举值,您可以使这些值彼此无法区分。如果您尝试打印值的名称,则会输入未定义的行为区域,这意味着实现可以免费为您提供任何相关名称。为什么它选择一个而不是另一个是一个实现细节,并且可能随时改变。
  • 返回哪个枚举名称是未记录的行为,see my answer here
  • 这确实是朗姆酒时间问题;p @LasseV.Karlsen

标签: c# .net enums


【解决方案1】:

似乎您已经发现编译器默认从零开始计数。

enum MyEnum
{
    Left, Right, Top = 0, Bottom = 0 // Bottom
}

翻译成这个

.class nested private auto ansi sealed MyEnum
    extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname int32 value__
    .field public static literal valuetype X/MyEnum Left = int32(0)
    .field public static literal valuetype X/MyEnum Right = int32(1)
    .field public static literal valuetype X/MyEnum Top = int32(0)
    .field public static literal valuetype X/MyEnum Bottom = int32(0)
}

运行时实际上大部分时间都与底层类型一起工作。所以有趣的是这里

static void Main()
{
    MyEnum a = MyEnum.Top;
    Console.WriteLine(a);

    Console.ReadKey();
}

甚至不使用实际的枚举成员:

.method private hidebysig static 
    void Main () cil managed 
{
    .maxstack 1
    .entrypoint
    .locals init (
        [0] valuetype X/MyEnum a
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: box X/MyEnum
    IL_0009: call void [mscorlib]System.Console::WriteLine(object)
    IL_000e: nop
    IL_000f: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    IL_0014: pop
    IL_0015: ret
}

它只使用值 0。使用Console.WriteLine 打印出哪个名称的决策从System.Enum.ToString 开始,在System.Type.GetEnumName(object) 中达到高潮。

public virtual string GetEnumName(object value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (!this.IsEnum)
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType");
    }
    Type type = value.GetType();
    if (!type.IsEnum && !Type.IsIntegerType(type))
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnumBaseTypeOrEnum"), "value");
    }
    Array enumRawConstantValues = this.GetEnumRawConstantValues();
    int num = Type.BinarySearch(enumRawConstantValues, value);
    if (num >= 0)
    {
        string[] enumNames = this.GetEnumNames();
        return enumNames[num];
    }
    return null;
}

如您所见,搜索要打印的名称的实际方法是通过字段名称进行二进制搜索(通过反射找到)。

这只是当前的实现,但可能会因不同的编译器和/或运行时版本而有所不同。语言规范不保证上述代码的任何特定顺序或结果。

【讨论】:

  • 它使用二进制搜索来查找要显示的名称的事实是未记录的行为,因此 OP 不应依赖代码总是为重复值返回相同的名称。此外,仅向枚举添加 1 个值可能会改变结果。
  • 是的,它只是我使用的编译器和运行时版本的当前实现。我已经为此添加了一段。
【解决方案2】:

枚举只是命名一些常量。

就像我们有一些水果一样。苹果、香蕉、橙子。我们有一个场景,如果一个婴儿 1 岁会得到橙色,如果 2 岁会得到香蕉,如果 3 岁会得到苹果。那我们怎么写呢?

一个解决方案是这样的:

    int fruiteType = 0;
    if(ageOfBaby == 1)
        fruiteType == 1;//assuming Orange = 1; 
    if(ageOfBaby == 2)
        fruiteType == 2;//assuming Banana = 2; 
    if(ageOfBaby == 3)
        fruiteType == 3;//assuming Apple = 3; 

我们可以巧妙地使用 Enum 来做到这一点。喜欢:

    enum Fruits {Orange, Banana, Apple};

    int fruiteType = 0;
    if(ageOfBaby == 1)
        fruiteType == (int)Fruits.Orange;
    if(ageOfBaby == 2)
        fruiteType == (int)Fruits.Banana;
    if(ageOfBaby == 3)
        fruiteType == (int)Fruits.Apple;

默认情况下,您将分别在上述条件下的 fruiteType 变量中获得 0、1、2。如果你想改变默认值,你可以定义如下:

  enum Fruits {Orange = 1, Banana, Apple};

为什么这很重要。当我们假设这样的值时,我们可能会忘记顺序,从而造成麻烦。但是当我们给它们命名时,我们永远不会忘记水果类型 = 1 的值。

谢谢

【讨论】:

  • 感谢您的努力,但这并不能以任何方式回答 OP 的问题。
猜你喜欢
  • 2012-06-26
  • 2012-06-19
  • 1970-01-01
  • 2015-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-05
  • 1970-01-01
相关资源
最近更新 更多