添加新成员时枚举显示不一致

Inconsistent enum display when new members are added

我有以下枚举声明

enum Vehicle1
{
    None,
    Bicycle,
    //Motorcycle,
    //Car,
    //Van,
    //Lorry,
    //Aeroplane,
    //Rocket,
    //TimeMachine,

    Basikal = Bicycle,
    Fahrrad = Basikal,
    Velo = 1,
}

enum Vehicle3
{
    None,
    Bicycle,
    Motorcycle,
    Car,
    //Van,
    //Lorry,
    //Aeroplane,
    //Rocket,
    //TimeMachine,

    Basikal = Bicycle,
    Fahrrad = Basikal,
    Velo = 1,
}

当我使用以下命令打印输出时,

Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle1.Bicycle, Vehicle1.Bicycle));
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle1.Basikal, Vehicle1.Basikal));
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle1.Fahrrad, Vehicle1.Fahrrad));
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle1.Velo, Vehicle1.Velo));      
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle3.Bicycle, Vehicle3.Bicycle));
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle3.Basikal, Vehicle3.Basikal));
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle3.Fahrrad, Vehicle3.Fahrrad));
Console.WriteLine(string.Format("{0}: {1}", (int)Vehicle3.Velo, Vehicle3.Velo));      

我在控制台得到以下输出:

1: Basikal
1: Basikal
1: Basikal
1: Basikal
1: Fahrrad
1: Fahrrad
1: Fahrrad
1: Fahrrad

我能观察到的这两个枚举声明之间的唯一区别是 Vehicle3 定义了更多的成员。 Vehicle1.Bicycle 和 Vehicle3.Bicycle 的基础值相同,即 1,但为什么输出不同?

c# 枚举在转换为整数时是基于零索引的(除非明确设置)。因此,在您的示例中,Velo 等于 1,这意味着它等于现有项目 (Bicycle)。

这样想他们:

enum Vehicle1
    {
        None = 0,
        Bicycle = 1,
        //Motorcycle = 2,
        //Car = 3,
        //Van = 4,
        //Lorry = 5,
        //Aeroplane = 6,
        //Rocket = 7,
        //TimeMachine = 8,

        Basikal = Bicycle,//set to 1
        Fahrrad = Basikal, // also set to 1
        Velo = 2, // this is explictitly set as 2, becuase of the commented items above, otherwise the auto index would be 9
    }

but why are the output differ?

您可以使用 ILSpy 工具或其他类似工具来查看 Enum.ToString() 方法是如何实现的。

我快速浏览了一下。它最终到达 Type.GetEnumName 方法。在 ILSpy 中查看此方法,似乎首先,.NET 获取枚举类型的所有可能值名称(通过 Type.GetEnumNames),然后对值执行 binary search(在您的示例中为 1 ).

在您的示例中,这是 typeof(Vehicle1).GetEnumNames() 的结果:

[0]: "None"
[1]: "Bicycle"
[2]: "Basikal"
[3]: "Fahrrad"
[4]: "Velo"

以及typeof(Vehicle3).GetEnumNames()的结果:

[0]: "None"
[1]: "Velo"
[2]: "Bicycle"
[3]: "Fahrrad"
[4]: "Basikal"
[5]: "Motorcycle"
[6]: "Car"

根据documentation,当多个枚举项具有相同的值时,顺序未指定。

所以二进制搜索在中间。对于 Vehicle1,它将以 Basikal 开头,而对于 Vehicle3,它将以 Fahrrad 开头。搜索立即停止,因为两种情况下的值都是 1。

此问题已在 MSDN page for Enum.ToString - Remarks section

中说明

If multiple enumeration members have the same underlying value and you attempt to retrieve the string representation of an enumeration member's name based on its underlying value, your code should not make any assumptions about which name the method will return. For example, the following enumeration defines two members, Shade.Gray and Shade.Grey, that have the same underlying value.

enum Shade {
     White = 0, Gray = 1, Grey = 1, Black = 2  }

以下方法调用尝试检索 Shade

成员的名称

enumeration whose underlying value is 1. The method can return either "Gray" or "Grey", and your code should not make any assumptions about which string will be returned.

string shadeName = ((Shade) 1).ToString("F");

注意string.Format("{0}: {1}", (int)Vehicle1.Bicycle, Vehicle1.Bicycle)基本上是 string.Format("{0}: {1}", (int)Vehicle1.Bicycle, Vehicle1.Bicycle.ToString())

基本上是因为一些枚举成员具有相同的值。 ToString() 结果不可预测。

几个答案已经解释了您问题中行为背后的原因。

分析 System.Enum 实现的好地方。这是 Microsoft Reference Resources 的 link(请注意,这是指向 .NET Framework 4.6)

如果你彻底走一走

1) GetName 方法

    [System.Runtime.InteropServices.ComVisible(true)]
    public static String GetName(Type enumType, Object value)
    {
        if (enumType == null)
            throw new ArgumentNullException("enumType");
        Contract.EndContractBlock();

        return enumType.GetEnumName(value);
    }

2) 深入研究 enumType.GetEnumName(value)

    public virtual string GetEnumName(object value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        if (!IsEnum)
            throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType");
        Contract.EndContractBlock();

        Type valueType = value.GetType();

        if (!(valueType.IsEnum || Type.IsIntegerType(valueType)))
            throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnumBaseTypeOrEnum"), "value");

        Array values = GetEnumRawConstantValues();
        int index = BinarySearch(values, value);

        if (index >= 0)
        {
            string[] names = GetEnumNames();
            return names[index];
        }

        return null;
    }

3) 关注这两行

Array values = GetEnumRawConstantValues();
int index = BinarySearch(values, value);

3.1) GetEnumRawConstantValues 方法

    private Array GetEnumRawConstantValues()
    {
        string[] names;
        Array values;
        GetEnumData(out names, out values);
        return values;
    }

3.1.1) GetEnumData 方法

    // This will return enumValues and enumNames sorted by the values.
    private void GetEnumData(out string[] enumNames, out Array enumValues)
    {
        Contract.Ensures(Contract.ValueAtReturn<String[]>(out enumNames) != null);
        Contract.Ensures(Contract.ValueAtReturn<Array>(out enumValues) != null);

        FieldInfo[] flds = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

        object[] values = new object[flds.Length];
        string[] names = new string[flds.Length];

        for (int i = 0; i < flds.Length; i++)
        {
            names[i] = flds[i].Name;
            values[i] = flds[i].GetRawConstantValue();
        }

        // Insertion Sort these values in ascending order.
        // We use this O(n^2) algorithm, but it turns out that most of the time the elements are already in sorted order and
        // the common case performance will be faster than quick sorting this.
        IComparer comparer = Comparer.Default;
        for (int i = 1; i < values.Length; i++)
        {
            int j = i;
            string tempStr = names[i];
            object val = values[i];
            bool exchanged = false;

            // Since the elements are sorted we only need to do one comparision, we keep the check for j inside the loop.
            while (comparer.Compare(values[j - 1], val) > 0)
            {
                names[j] = names[j - 1];
                values[j] = values[j - 1];
                j--;
                exchanged = true;
                if (j == 0)
                    break;
            }

            if (exchanged)
            {
                names[j] = tempStr;
                values[j] = val;
            }
        }

        enumNames = names;
        enumValues = values;
    }

3.2) BinarySearch 方法

private static int BinarySearch(Array array, object value)
    {
        ulong[] ulArray = new ulong[array.Length];
        for (int i = 0; i < array.Length; ++i)
            ulArray[i] = Enum.ToUInt64(array.GetValue(i));

        ulong ulValue = Enum.ToUInt64(value);

        return Array.BinarySearch(ulArray, ulValue);
    }

从 3.1.1 和 3.2 可以看到,枚举值正在按升序排序(插入排序),然后在结果数组中对值执行二进制搜索。