Подтвердить что ты не робот

Какую константу Enum я получу, если значения Enum одинаковы

Есть ли логика, к которой я получаю константу, если имеется более одной константы перечисления, которая имеет такое же значение?

Я попробовал варианты ниже, но не смог получить разумную логику.

Основной метод:

public class Program
{
    public static void Main(string[] args)
    {
        Test a = 0;
        Console.WriteLine(a);
    }
}

Первая попытка:

enum Test
{
    a1=0,
    a2=0,
    a3=0,
    a4=0,
}

Вывод:

a2

Вторая попытка:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4=0,
}

Вывод:

a4

Третья попытка:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4,
}

Вывод:

a2

Четвертая попытка:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4
}

Вывод:

a1
4b9b3361

Ответ 1

Документация действительно обращается к этому:

Если несколько элементов перечисления имеют одно и то же базовое значение, и вы пытаетесь получить строковое представление имени члена перечисления на основе его базового значения, ваш код не должен делать никаких предположений о том, какое имя возвращает метод.

(выделено курсивом)

Однако это не означает, что результат случайный. Это означает, что это деталь реализации, которая может быть изменена. Реализация может полностью измениться только с помощью патча, может быть разной для компиляторов (MONO, Roslyn и т.д.) И быть разными на разных платформах.

Если ваша система спроектирована так, что требуется обратный просмотр для перечислений со временем и платформами, то не используйте Enum.ToString. Или измените свой дизайн, чтобы он не зависел от этой детали, или напишите свой собственный метод, который будет последовательным.

Таким образом, вы не должны писать код, который зависит от этой реализации, или вы рискуете, что он изменится без вашего ведома в будущей версии.

Ответ 2

TL; DR: все поля перечисления будут извлекаться с помощью отражения, затем сортировка вставки и двоичная информация будут искать первое совпадающее значение.


Цепочка вызовов выглядела так:

Enum.Tostring();
Enum.InternalFormat(RuntimeType eT, Object value);
Enum.GetName(Type enumType, Object value);
Type.GetEnumName(object value);

Type.GetEnumName(object value) реализуется как таковой:

    public virtual string GetEnumName(object value)
    {
        // standard argument guards...

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

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

        return null;
    }

Оба GetEnumRawConstantValues() и GetEnumNames() полагаются на GetEnumData(out string[] enumNames, out Array enumValues):

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

При этом GetFields(BindingFlags bindingAttr) приводит к методу abstract, но поиск "GetFields" на msdn даст вам EnumBuilder.GetFields(BindingFlags bindingAttr). И если мы следуем его цепочке вызовов:

EnumBuilder.GetFields(BindingFlags bindingAttr);
TypeBuilder.GetFields(BindingFlags bindingAttr);
RuntimeType.GetFields(BindingFlags bindingAttr);
RuntimeType.GetFieldCandidates(String name, BindingFlags bindingAttr, bool allowPrefixLookup);
RuntimeTypeCache.GetFieldList(MemberListType listType, string name);
RuntimeTypeCache.GetMemberList<RuntimeFieldInfo>(ref MemberInfoCache<T> m_cache, MemberListType listType, string name, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.GetMemberList(MemberListType listType, string name, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.Populate(string name, MemberListType listType, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.GetListByName(char* pName, int cNameLen, byte* pUtf8Name, int cUtf8Name, MemberListType listType, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.PopulateFields(Filter filter);
// and from here, it is a wild ride...

Итак, я приведу Type.GetFields примечания:

Метод GetFields не возвращает поля в определенном порядке, например, в алфавитном порядке или в порядке объявления. Ваш код не должен зависеть от порядка, в котором возвращаются поля, поскольку этот порядок меняется.