Почему только целые числа? - PullRequest
10 голосов
/ 04 июня 2010

Я пишу на C # уже семь лет, и я продолжаю удивляться, почему перечисления должны быть целочисленного типа? Не было бы неплохо сделать что-то вроде:

enum ErrorMessage 
{ 
     NotFound: "Could not find",
     BadRequest: "Malformed request"
}

Это выбор языка design или существуют фундаментальные несовместимости на уровне компилятора, CLR или IL?

Есть ли в других языках перечисления со строковым или сложным (т.е. объектным) типом? Какие языки?

(Мне известны обходные пути; мой вопрос: зачем они нужны?)

РЕДАКТИРОВАТЬ: "обходные пути" = атрибуты или статические классы с consts:)

Ответы [ 10 ]

8 голосов
/ 04 июня 2010

Цель Enum - дать более значимые значения целым числам. Вы ищете что-то еще, кроме Enum. Перечисления совместимы с более старыми интерфейсами Windows API и COM, а также имеют большую историю на других платформах.

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

Или, может быть, вы пытаетесь ограничить значения некоторых специализированных типов только определенными строковыми значениями? Но как он хранится и как он отображается, это могут быть две разные вещи - зачем использовать больше места, чем необходимо для хранения значения?

И если вы хотите, чтобы что-то подобное читалось в каком-то постоянном формате, просто сделайте утилиту или метод Extension, чтобы выплюнуть это.

Этот ответ немного беспорядочный, потому что причин так много. Сравнение двух строк на достоверность намного дороже, чем сравнение двух целых чисел. Сравнение буквенных строк с известными перечислениями для статической проверки типов было бы неоправданным. Локализация была бы ... странной. Совместимость с будет нарушена. Перечисления как флаги были бы бессмысленными / неработающими.

Это Enum. Это то, что делают Enums! Они цельные!

7 голосов
/ 04 июня 2010

Возможно, использовать атрибут description из System.ComponentModel и написать вспомогательную функцию для извлечения связанной строки из значения перечисления? (Я видел это в кодовой базе, с которой я работаю, и мне показалось, что это вполне разумная альтернатива)

enum ErrorMessage 
{ 
     [Description("Could not find")]
     NotFound,
     [Description("Malformed request")]
     BadRequest
}
6 голосов
/ 04 июня 2010

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

  • ToString вернет другую строку с именем перечисления. То есть ErrorMessage.NotFound.ToString() будет «Не удалось найти» вместо «Не найден».
  • И наоборот, с Enum.Parse что бы это делало? Будет ли он по-прежнему принимать строковое имя перечисления, как и для целочисленных перечислений, или он работает со строкой value ?
  • Вы не сможете реализовать [Flags], потому что то, что ErrorMessage.NotFound | ErrorMessage.BadRequest будет равно в вашем примере (я знаю, что это не имеет смысла в данном конкретном случае, и я полагаю, вы могли бы просто сказать, что [Flags] не допускается при строковых перечислениях, но это все еще кажется мне недостатком)
  • Хотя сравнение errMsg == ErrorMessage.NotFound может быть реализовано как простое эталонное сравнение, errMsg == "Could not find" должно быть реализовано как сравнение строк.

Я не могу думать о каких-либо преимуществах, тем более, что так легко создать свои собственные значения перечисления словаря, сопоставляя их с "пользовательскими" строками.

4 голосов
/ 04 июня 2010

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

В .Net перечисления получили дополнительное преимущество internal representation <-> the string used to define them. Это небольшое изменение добавляет некоторые недостатки версий, но улучшает перечисления в C ++.

Ключевое слово enum используется для объявления перечисление, отдельный тип, который состоит из набора именованных констант вызывается список перечислителей.

Ссылка: MSDN

Ваш вопрос связан с выбранным механизмом хранения, целым числом. Это просто деталь реализации. Мы можем заглянуть только под обложки этого простого типа, чтобы сохранить бинарную совместимость. Иначе перечисления имели бы очень ограниченную полезность.

В: Так почему перечисления используют целочисленную память? Как уже отмечали другие:

  1. Целые числа быстро и легко сравнивать.
  2. Целые числа легко и быстро объединяются (поразрядно для [Flags] стилей)
  3. С целыми числами легко реализовать перечисления.

* ничего из этого не относится к .net, и, похоже, разработчики CLR явно не чувствовали необходимости что-либо менять или добавлять к ним золотое покрытие.

Это не значит, что ваш синтаксис не совсем непривлекателен. Но оправданы ли усилия по реализации этой функции в CLR и во всех компиляторах? За всю работу, связанную с этим, она действительно купила вам что-то, чего вы еще не могли достичь (с классами)? Мое чувство кишки - нет, никакой реальной пользы нет. (Есть сообщение от Эрика Липперта , на которое я хотел сослаться, но не смог его найти)

Вы можете написать 10 строк кода, чтобы реализовать в пользовательском пространстве то, что вы пытаетесь достичь без головной боли при смене компилятора. Ваш код пользовательского пространства легко поддерживается с течением времени - хотя, возможно, он не так хорош, как если бы он был встроенным, но в конце концов это то же самое. Вы даже можете использовать шаблон генерации кода T4 , если вам нужно поддерживать многие из ваших пользовательских значений enum-esque в вашем проекте.

Итак, перечисления настолько сложны, насколько это необходимо.


2 голосов
/ 04 июня 2010

Не совсем отвечаю на ваш вопрос, но представляю альтернативы строковым перечислениям.

public struct ErrorMessage  
{  
     public const string NotFound="Could not find";
     public const string BadRequest="Malformed request";
} 
1 голос
/ 04 июня 2010

Строго говоря, внутреннее представление перечисления не должно иметь значения, потому что по определению они являются перечисляемыми типами. Это означает, что

public enum PrimaryColor { Red, Blue, Yellow }

представляет набор значений.

Во-первых, некоторые наборы меньше, тогда как другие наборы больше. Следовательно, .NET CLR позволяет основывать перечисление на целочисленном типе, так что размер домена для перечисляемых значений можно увеличивать или уменьшать, т. Е. Если перечисление основано на байте, то это перечисление не может содержать более 256 отличные значения, тогда как одно основанное на long может содержать 2 ^ 64 различных значений. Это связано с тем, что long в 8 раз больше байта.

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

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

По большей части, я бы сказал, что представление перечислений целочисленными типами, по-видимому, является выбором дизайна CLR и / или CLS, хотя это, вероятно, не так уж и сложно.

1 голос
/ 04 июня 2010

Это решение по языку - например, Java enum напрямую не соответствует int, но вместо этого является фактическим классом . Есть много хороших трюков, которые дает вам int enum - вы можете побитировать их для флагов, итерировать их (добавляя или вычитая 1) и т. Д. Но есть и некоторые недостатки этого - отсутствие дополнительных метаданных, приведение любого недопустимое значение int и т. д.

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

Статические члены только для чтения дают вам эффект сложных перечислений, но не требуют накладных расходов, если вам это не нужно.

 static class ErrorMessage {
     public string Description { get; private set; }
     public int Ordinal { get; private set; }
     private ComplexEnum() { }

     public static readonly NotFound = new ErrorMessage() { 
         Ordinal = 0, Description = "Could not find" 
     };
     public static readonly BadRequest = new ErrorMessage() { 
         Ordinal = 1, Description = "Malformed Request" 
     };
 }
1 голос
/ 04 июня 2010

Возможно, потому что тогда это не имело бы смысла:

enum ErrorMessage: string
{
    NotFound,
    BadRequest
}
0 голосов
/ 15 июня 2010

Хотя это также считается «альтернативой», вы все равно можете добиться большего успеха, чем просто набор констант:

struct ErrorMessage
{
    public static readonly ErrorMessage NotFound =
        new ErrorMessage("Could not find");
    public static readonly ErrorMessage BadRequest =
        new ErrorMessage("Bad request");

    private string s;

    private ErrorMessage(string s)
    {
        this.s = s;
    }

    public static explicit operator ErrorMessage(string s)
    {
        return new ErrorMessage(s);
    }

    public static explicit operator string(ErrorMessage em)
    {
        return em.s;
    }
}

Единственный улов здесь заключается в том, что, как и любой тип значения, этот имеет значение по умолчанию, которое будет иметь s==null. Но это на самом деле не отличается от перечислений Java, которые сами могут быть null (являющимися ссылочными типами).

Как правило, расширенные перечисления в стиле Java пересекают грань между фактическими перечислениями и синтаксическим сахаром для запечатанной иерархии классов. Независимо от того, такие сахара хорошая идея или нет спорно.

0 голосов
/ 04 июня 2010

Основным преимуществом целочисленных перечислений является то, что они не занимают много места в памяти. Экземпляр перечисления, поддерживаемого System.Int32 по умолчанию, занимает всего 4 байта памяти и может быстро сравниваться с другими экземплярами этого перечисления.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...