Что определяет, какое имя выбрано при вызове 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();
Также см. предыдущий параграф - это документация, в которой показано, как получить все имена