Что определяет, какое имя выбрано при вызове ToString() для значения перечисления, которое имеет несколько соответствующих имен?

Что определяет, какое имя выбрано при вызове ToString() по значению enum, которое имеет несколько соответствующих имен?

Ниже следует подробное объяснение вопроса.

Я определил, что это не определено однозначно ни одним из: алфавитным порядком; порядок объявления; ни, длина имени.

Например, учтите, что я хочу иметь перечисление, где числовые значения соответствуют непосредственно практическому использованию (например, значения rgb для цвета).

public enum RgbColor 
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

Теперь, с этим перечислением, вызов default(RgbColor) вернет значение rgb для черного. Скажем, я не хочу, чтобы значение по умолчанию было черным, потому что я хочу, чтобы дизайнеры пользовательских возможностей могли использовать вызов "По умолчанию", когда у них нет конкретных инструкций о том, какой цвет использовать. На данный момент значение по умолчанию для дизайнеров пользовательского интерфейса на самом деле является "голубым", но это может измениться. Итак, я добавляю дополнительное определение TextDefault в перечисление, и теперь оно выглядит так:

public enum RgbColorWithTextDefaultFirst
{
    TextDefault = 0x0000ff,
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

Теперь, я проверил это, и я обнаружил, что вызов RgbColorWithTextDefaultFirst.TextDefault.ToString() и RgbColorWithTextDefaultFirst.Blue.ToString() обе дает "синий". Итак, я понял, что имя, объявленное последним, перезапишет имя предыдущих объявлений. Чтобы проверить мое предположение, я написал:

public enum RgbColorWithTextDefaultLast
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff,
    TextDefault = 0x0000ff 
}

Однако, к моему удивлению, RgbColorWithTextDefaultLast.Blue.ToString() и RgbColorWithTextDefaultLast.TextDefault.ToString(). Моя следующая догадка заключается в том, что он сортирует имена по алфавиту и возвращает первый. Чтобы проверить это, я пытаюсь:

public enum RgbColorWithATextDefaultFirst
{
    ATextDefault = 0x0000ff,
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

public enum RgbColorWithATextDefaultLast
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff,
    ATextDefault = 0x0000ff
}

Теперь, для всех четырех из RgbColorWithATextDefaultFirst.ATextDefault.ToString(), RgbColorWithATextDefaultFirst.Blue.ToString(), RgbColorWithATextDefaultLast.ATextDefault.ToString(), RgbColorWithATextDefaultLast.Blue.ToString(), я получаю "Синий". Я понимаю, что есть еще один отличительный фактор, который является длиной строки. Я предполагаю, что выбранное имя определяется длиной строки имени. Итак, мой тест заключается в использовании этих объявлений:

public enum RgbColorWithALast
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff,
    A = 0x0000ff
}

public enum RgbColorWithAFirst
{
    A = 0x0000ff,
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

Теперь угадайте, какое значение я получил для всего: RgbColorWithAFirst.A.ToString(); RgbColorWithAFirst.Blue.ToString(); RgbColorWithALast.A.ToString(), RgbColorWithALast.Blue.ToString(). Это правильно, "Синий".

В этот момент я отказался от попыток выяснить, что определяет это, гадая. Я открыл рефлектор, и я собираюсь взглянуть и попытаюсь понять это, но я решил, что задаю здесь вопрос, чтобы узнать, знает ли кто-нибудь, кто здесь уже знает ответ, а именно: Что определяет какое имя выбрано при вызове ToString() по значению enum, которое имеет несколько соответствующих имен?

Ответы

Ответ 1

Я мог бы зайти слишком далеко, но я думаю, что он решил двоичный поиск отсортированных значений, и поэтому может зависеть от четности общего количества значений. Вы можете проиллюстрировать это своим последним примером (RgbColorWithAFirst и RgbColorWithALast), указав другое значение в обоих - тогда вы получите A из всех вызовов ToString.

Я пришел сюда, декомпилировав mscorlib (4.0) и отметив, что в итоге мы получим вызов Array.BinarySearch в отсортированном массиве объявленных значений. Естественно, двоичный поиск останавливается, как только он получает соответствие, поэтому для переключения между двумя идентичными значениями самый простой способ - изменить дерево поиска, добавив дополнительное значение.

Конечно, это детализация реализации и на нее нельзя положиться. Мне кажется, что в вашем случае вам будет лучше всего использовать DescriptionAttribute для значений enum, где вы хотите явно указать отображаемое значение, и вспомогательный метод, например:

public static class EnumExtensions
{
    public static string Description(this Enum value)
    {
        var field = value.GetType().GetField(value.ToString());
        var attribute = Attribute.GetCustomAttribute(
                            field, 
                            typeof (DescriptionAttribute)) 
                        as DescriptionAttribute;

        return attribute == null ? value.ToString() : attribute.Description;
    }
}

Ответ 2

Документация предупреждает, что повторяющиеся значения будут приводить к ошибкам.
У вас есть повторяющиеся значения и возникают ошибки - не удивительно.

Вы получаете первый равный.
Равное основано исключительно на значении.
Вы не контролируете порядок, в котором вычисляется перечисление.

Как указано в комментарии, перечисление ожидает уникальные значения для типа.

Типы перечислений (Руководство по программированию на С#)

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

По-видимому, вы обнаружили одну из ошибок высокого риска с повторяющимися значениями типа.

Я характеризую эту ошибку как не детерминированную, так как я рассматриваю изменение порядка, но те же данные, что и тот же вход, который ничего не дает в состоянии spec, означает, что вход enum зависит от порядка. ОП рассматривает разные порядки как разные данные. С предположением ОП не будет характеризовать это не детерминированным. Все еще справедливо, чтобы охарактеризовать его как ошибку, вызванную повторяющимися значениями для типа.

Ожидается еще одна ссылка, подразумевающая уникальность

Enum.GetName

Возврат - это строка (не строка []).

Равны основаны исключительно на значении.

Enum.Equals

GetName будет соответствовать первому значению.
Это мое определение недетерминированных. Как вы можете узнать, что у вас есть, если есть, если они считаются равными?

Подозреваемый Microsoft не применяет уникальность значений типа перечисления из-за накладных расходов.

OP ожидает, что Enum явно связывает значение с строкой A (например, KVP), когда нет указаний на этот тип ассоциации.
Документация явно предостерегает от принятия этого предположения.
Где в документации указано, что Enum реализуется как набор пары значений, класса или стойки?
Результаты и методы указывают, что Enum является строкой и типом значения (кроме char) со свободной связью.

Возможная работа.

Словарь не требует уникальных значений.

public enum RgbColor : byte
{
    Black,
    Red,
    Green,
    Blue,
    White,
    Default
}

static void Main(string[] args)
{
    Dictionary<RgbColor, Int32> ColorRGB = new Dictionary<RgbColor, int>();
    ColorRGB.Add(RgbColor.Black, 0x000000);
    ColorRGB.Add(RgbColor.Default, 0x0000ff);
    ColorRGB.Add(RgbColor.Blue, 0x0000ff);
    ColorRGB.Add(RgbColor.Green, 0x00ff00);
    ColorRGB.Add(RgbColor.White, 0xffffff);

    Debug.WriteLine(ColorRGB[RgbColor.Blue].ToString("X6"));
    Debug.WriteLine(ColorRGB[RgbColor.Default].ToString("X6"));
    Debug.WriteLine(ColorRGB[RgbColor.Black].ToString("X6"));
    Debug.WriteLine(ColorRGB[RgbColor.White].ToString("X6"));

Ответ 3

Поведение undefined. Даже если вы можете показать, какой результат для определенной версии .NEt, это может измениться. См. Документацию enum.toString, в которой говорится:

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

enum Shade {     Белый = 0, Серый = 1, Серый = 1, Черный = 2}

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

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

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