Как преобразовать из System.Enum в базовое целое число?

96

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

Например, я хочу примерно следующее:

// Trivial example, not actually what I'm doing.
class Converter
{
    int ToInteger(System.Enum anEnum)
    {
        (int)anEnum;
    }
}

Но это, похоже, не работает. Resharper сообщает, что вы не можете привести выражение типа System.Enum к типу int.

Теперь я придумал это решение, но я бы предпочел что-то более эффективное.

class Converter
{
    int ToInteger(System.Enum anEnum)
    {
        return int.Parse(anEnum.ToString("d"));
    }
}

Какие-либо предложения?

orj
источник
1
Я считаю, что жалуется компилятор, а не Resharper.
Кугель
1
Не обязательно. У меня есть метод расширения в System.Enum, и иногда Resharper решает пожаловаться: не удается преобразовать тип аргумента экземпляра Some.Cool.Type.That.Is.An.Enum в System.Enum, когда он, несомненно, является перечислением. Если я компилирую и запускаю код, он работает нормально. Если я затем закрою VS, удалю кеш Resharper и снова запустю его, все будет в порядке после повторного сканирования. Для меня это какой-то сбой с кешем. Может быть, и с ним.
Мир
@Mir Я тоже "жаловался" на ReSharper. То же исправление для меня. Не уверен, почему он смешивает эти типы, но это определенно не компилятор.
akousmata

Ответы:

137

Если ты не хочешь кастовать,

Convert.ToInt32()

может сделать свое дело.

Прямая трансляция (через (int)enumValue) невозможна. Обратите внимание , что это также будет «опасным» , поскольку перечисление может иметь различные типы , лежащие в основе ( int, long, byte...).

Более формально: System.Enumне имеет прямого отношения наследования с Int32(хотя оба являются ValueTypes), поэтому явное приведение не может быть правильным в системе типов

MartinStettner
источник
3
Converter.ToInteger(MyEnum.MyEnumConstant);здесь не будет ошибок. Пожалуйста, отредактируйте эту часть.
nawfal 01
Спасибо, @nawfal, ты прав. Я отредактировал ответ (и узнал что-то новое :) ...)
MartinStettner
Не работает, если базовый тип отличается от int
Алекс Жуковский
@YaroslavSivakov это не сработает, например, для longenum.
Алексей Жуковский
System.Enum- это класс, поэтому это ссылочный тип. Значения, вероятно, заключены в рамку.
Teejay
45

Я заставил его работать, приведя к объекту, а затем к int:

public static class EnumExtensions
{
    public static int ToInt(this Enum enumValue)
    {
        return (int)((object)enumValue);
    }
}

Это уродливый и, наверное, не лучший способ. Я буду продолжать возиться с этим, чтобы посмотреть, смогу ли я придумать что-нибудь получше ...

РЕДАКТИРОВАТЬ: как раз собирался опубликовать, что Convert.ToInt32 (enumValue) также работает, и заметил, что MartinStettner превзошел меня в этом.

public static class EnumExtensions
{
    public static int ToInt(this Enum enumValue)
    {
        return Convert.ToInt32(enumValue);
    }
}

Контрольная работа:

int x = DayOfWeek.Friday.ToInt();
Console.WriteLine(x); // results in 5 which is int value of Friday

РЕДАКТИРОВАТЬ 2: В комментариях кто-то сказал, что это работает только в С # 3.0. Я только что протестировал это в VS2005, и это сработало:

public static class Helpers
{
    public static int ToInt(Enum enumValue)
    {
        return Convert.ToInt32(enumValue);
    }
}

    static void Main(string[] args)
    {
        Console.WriteLine(Helpers.ToInt(DayOfWeek.Friday));
    }
BFree
источник
Я дал вам +1, но это решение будет работать только с C # 3.0 и выше.
Вадим
Думаю, это устраивает только компилятор. Удалось ли вам проверить это во время выполнения? Я не мог передать значение этой функции ...
Мартин Стеттнер,
Потому что это метод расширения? Или что-то другое в перечислениях в старых версиях C #?
BFree
Добавил тест в конец поста. Я попробовал это, и у меня это сработало.
BFree
@BFree: похоже, работает только с методами расширения. Я попробовал это с помощью функции «старого стиля» (в C # 2.0) и не смог найти синтаксис, который бы фактически передавал ей какое-либо значение (об этом был мой предыдущий комментарий)
MartinStettner
15

Если вам нужно преобразовать любое перечисление в его базовый тип (не все перечисления поддерживаются int), вы можете использовать:

return System.Convert.ChangeType(
    enumValue,
    Enum.GetUnderlyingType(enumValue.GetType()));
Дрю Ноукс
источник
5
Это единственный надежный ответ.
Rudey
Это причина распаковки бокса?
b.ben 07
@ b.ben да. Convert.ChangeTypeпринимает objectв качестве первого аргумента и возвращает object.
Дрю Ноукс
Да, я действительно согласен с @Rudey в этом, в любом случае вам следует провести тесты производительности. Решение BFree на сегодняшний день является самым быстрым. Примерно в восемь раз быстрее. В любом случае мы все еще говорим о клещах.
Двадцать
10

Зачем нужно изобретать велосипед с помощью вспомогательного метода? Совершенно законно использоватьenum значение к его базовому типу.

Он меньше печатает и, на мой взгляд, более читабелен ...

int x = (int)DayOfWeek.Tuesday;

... а не что-то вроде ...

int y = Converter.ToInteger(DayOfWeek.Tuesday);
// or
int z = DayOfWeek.Tuesday.ToInteger();
Лука
источник
6
Я хочу преобразовать ЛЮБОЕ значение перечисления. Т.е. у меня есть множество классов, у которых есть поля перечисления, которые обрабатываются некоторым кодом обычным образом. Вот почему простое приведение к int не подходит.
orj
@orj, я не совсем понимаю, что вы имеете в виду. Вы можете привести пример, показывающий, какое использование вам нужно?
LukeH
2
Иногда вы не можете напрямую привести перечисление, например, в случае универсального метода. Поскольку общие методы не могут быть ограничены перечислениями, вы должны ограничить их чем-то вроде структуры, которая не может быть преобразована в int. В этом случае вы можете использовать Convert.ToInt32 (enum) для этого.
Дэниел Т.
Мне нравится идея, что метод расширения ToInteger () следует тому же формату, что и ToString (). Я думаю, что, с одной стороны, менее читабельно приводить к int, когда вам нужно значение int, и использовать ToString (), когда нам нужно строковое значение, особенно когда код находится рядом.
Луиза Эгглтон
Кроме того, использование метода расширения может повысить последовательность вашего кода. Поскольку, как указал @nawfal, существует несколько способов получить значение int из перечисления, создание метода расширения и обеспечение его использования означает, что каждый раз, когда вы получаете значение int перечисления, вы используете тот же метод
Луиза Эгглтон
4

Из моего ответа здесь :

Дано eкак в:

Enum e = Question.Role;

Тогда работают такие:

int i = Convert.ToInt32(e);
int i = (int)(object)e;
int i = (int)Enum.Parse(e.GetType(), e.ToString());
int i = (int)Enum.ToObject(e.GetType(), e);

Последние два уродливые. Первый должен быть более читабельным, хотя второй намного быстрее. Или может быть метод расширения является лучшим, лучшим из обоих миров.

public static int GetIntValue(this Enum e)
{
    return e.GetValue<int>();
}

public static T GetValue<T>(this Enum e) where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
    return (T)(object)e;
}

Теперь вы можете позвонить:

e.GetValue<int>(); //or
e.GetIntValue();
науфал
источник
Вы уверены, что второй быстрее? Я предполагаю, что он упакует перечисление, а затем распакует обратно в int?
yoyo
1
@yoyo eпеременная уже имеет упакованный экземпляр фактического типа значения, то есть enum. Когда пишешь Enum e = Question.Role;, уже в коробке. Вопрос в том, чтобы преобразовать коробку System.Enumобратно в базовый тип int (распаковка). Итак, распаковка - это только проблема производительности. (int)(object)eпрямой вызов распаковки; да должно быть быстрее, чем другие подходы. Смотрите это
nawfal
Спасибо за объяснение. У меня сложилось ошибочное впечатление, что экземпляр System.Enum был распакованным значением. См. Это тоже - msdn.microsoft.com/en-us/library/aa691158(v=vs.71).aspx
yoyo
@yoyo хм, да. Соответствующая цитата из ссылки: От любого enum-типа к типу System.Enum.
nawfal
1

Приведение из a System.Enumв a intотлично работает для меня (это также есть в MSDN ). Возможно, это ошибка Resharper.

jpoh
источник
1
Какую версию среды выполнения вы используете?
JoshBerke
2
ссылка показывает приведение подтипов System.Enum, а не System.Enumсебя.
nawfal
Похожая проблема была у меня с кешем Resharper. При закрытии VS и удалении кеша Resharper ошибка исчезла даже после полного повторного сканирования.
Мир
1

Поскольку перечисления ограничены байтами, sbyte, short, ushort, int, uint, long и ulong, мы можем сделать некоторые предположения.

Мы можем избежать исключений во время преобразования, используя самый большой доступный контейнер. К сожалению, неясно, какой контейнер использовать, потому что ulong взорвется для отрицательных чисел, а long - для чисел между long.MaxValue и ulong.MaxValue. Нам нужно переключаться между этими вариантами в зависимости от базового типа.

Конечно, вам все равно нужно решить, что делать, если результат не помещается в int. Я думаю, что кастинг - это нормально, но все же есть некоторые подводные камни:

  1. для перечислений на основе типа с полем, большим, чем int (long и ulong), возможно, некоторые перечисления будут оценивать одно и то же значение.
  2. приведение числа больше int.MaxValue вызовет исключение, если вы находитесь в отмеченной области.

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

public int ToInt(Enum e)
{
  unchecked
  {
    if (e.GetTypeCode() == TypeCode.UInt64)
      return (int)Convert.ToUInt64(e);
    else
      return (int)Convert.ToInt64(e);
  }
}
айзенпони
источник
-1

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

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

public static EnumHelpers
{
    public static T Convert<T, E>(E enumValue)
    {
        return (T)enumValue;
    }
}

Затем это можно было бы использовать так:

public enum StopLight: int
{
    Red = 1,
    Yellow = 2,
    Green = 3
}

// ...

int myStoplightColor = EnumHelpers.Convert<int, StopLight>(StopLight.Red);

Я не могу сказать наверняка, но приведенный выше код может даже поддерживаться выводом типа C #, позволяя следующее:

int myStoplightColor = EnumHelpers.Convert<int>(StopLight.Red);
Jrista
источник
1
К сожалению, в вашем примере E может быть любого типа. Обобщения не позволяют мне использовать Enum в качестве ограничения параметра типа. Не могу сказать: где T: Enum
Вадим
Однако вы можете использовать where T: struct. Это не так строго, как вы хотели бы, но он вырезал бы любой ссылочный тип (что, я думаю, именно то, что вы пытаетесь сделать.)
jrista
вы можете использовать: if (! typeof (E) .IsEnum) throw new ArgumentException («E должен быть перечислимым типом»);
diadiora
1
Это даже отдаленно неверно, статический помощник даже не действителен.
Nicholi
1
Почему кто-то должен использовать EnumHelpers.Convert<int, StopLight>(StopLight.Red);вместо (int)StopLight.Read;? Также идет речь о том System.Enum.
nawfal 01