Почему только интегральные перечисления?

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

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

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

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

(Я знаю обходных решениях, мой вопрос: зачем они нужны?)

EDIT: "обходные пути" = атрибуты или статические классы с константами:)

Ответ 1

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

Возможно, вас устраивают публичные элементы const структуры или класса.

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

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

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

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

Ответ 2

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

enum ErrorMessage 
{ 
     [Description("Could not find")]
     NotFound,
     [Description("Malformed request")]
     BadRequest
}

Ответ 3

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

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

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

Ответ 4

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

В .Net перечисления получили дополнительное преимущество internal representationthe string used to define them. Это небольшое изменение добавляет некоторые недостатки в версии, но улучшает их перечисление на С++.

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

Ссылка: msdn

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

Q: Итак, почему в перечислениях используется целочисленное хранилище? Как указывали другие,

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

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

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

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

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


Ответ 5

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

public struct ErrorMessage  
{  
     public const string NotFound="Could not find";
     public const string BadRequest="Malformed request";
} 

Ответ 6

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

enum ErrorMessage: string
{
    NotFound,
    BadRequest
}

Ответ 7

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

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

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

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

Ответ 8

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

public enum PrimaryColor { Red, Blue, Yellow }

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

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

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

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

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

Ответ 9

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

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

Ответ 10

Хотя он также считается "альтернативой", вы все равно можете сделать лучше, чем просто связку констант:

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-подобные расширенные enums пересекают границу между фактическими перечислениями и синтаксическим сахаром для запечатанной иерархии классов. Является ли такой сахар хорошей идеей или нет, является спорным.