Jak przekonwertować z System.Enum na podstawową liczbę całkowitą?

96

Chciałbym stworzyć ogólną metodę konwersji dowolnego typu pochodnego System.Enum na odpowiadającą mu wartość całkowitą, bez rzutowania, a najlepiej bez analizowania ciągu.

Np. Chcę coś takiego:

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

Ale to nie działa. Resharper zgłasza, że ​​nie można rzutować wyrażenia typu „System.Enum” na typ „int”.

Teraz wymyśliłem to rozwiązanie, ale wolałbym mieć coś wydajniejszego.

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

Jakieś sugestie?

orj
źródło
1
Uważam, że to kompilator narzeka, a nie Resharper.
Kugel
1
Niekoniecznie. Mam metodę rozszerzenia w System.Enum i czasami Resharper decyduje się narzekać: Nie można przekonwertować typu argumentu wystąpienia „Some.Cool.Type.That.Is.An.Enum” na „System.Enum”, gdy jest to niewątpliwie wyliczenie. Jeśli skompiluję i uruchomię kod, to działa dobrze. Jeśli następnie zamknę VS, zdmuchnę pamięć podręczną Resharper i uruchomię ją z powrotem, po ponownym skanowaniu wszystko będzie w porządku. Dla mnie to jakiś snafu cache. Dla niego może to być to samo.
Mir
@Mir. ReSharper też na to "narzekał". To samo rozwiązanie dla mnie. Nie jestem pewien, dlaczego te typy są pomieszane, ale na pewno nie jest to kompilator.
akousmata

Odpowiedzi:

137

Jeśli nie chcesz przesyłać,

Convert.ToInt32()

mógłby załatwić sprawę.

Bezpośrednia obsada (via (int)enumValue) nie jest możliwa. Należy pamiętać, że będzie to także „niebezpieczne”, ponieważ enum mogą mieć różne typy bazowe ( int, long, byte...).

Bardziej formalnie: System.Enumnie ma bezpośredniego związku dziedziczenia z Int32(chociaż oba są ValueTypes), więc jawne rzutowanie nie może być poprawne w systemie typów

MartinStettner
źródło
3
Converter.ToInteger(MyEnum.MyEnumConstant);nie poda tutaj żadnego błędu. Edytuj tę część.
nawfal
Dzięki, @nawfal, masz rację. Zredagowałem odpowiedź (i nauczyłem się czegoś nowego :) ...)
MartinStettner
Nie działa, jeśli typ bazowy różni się odint
Alex Zhukovskiy
@YaroslavSivakov to nie zadziała np. Dla longenum.
Alex Zhukovskiy
System.Enumjest klasą, więc jest typem referencyjnym. Wartości są prawdopodobnie w ramkach.
Teejay
44

Mam to do pracy, rzucając na obiekt, a następnie na int:

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

To jest brzydkie i prawdopodobnie nie jest to najlepszy sposób. Będę się tym bawić, aby zobaczyć, czy uda mi się wymyślić coś lepszego ....

EDYCJA: Właśnie miałem napisać, że Convert.ToInt32 (enumValue) również działa i zauważyłem, że MartinStettner mnie pokonał.

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

Test:

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

EDYCJA 2: W komentarzach ktoś powiedział, że działa to tylko w C # 3.0. Właśnie przetestowałem to w VS2005 w ten sposób i zadziałało:

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
źródło
Dałem ci +1, ale to rozwiązanie będzie działać tylko z C # 3.0 i nowszymi.
Vadim
Myślę, że to spełnia tylko kompilator. Czy udało Ci się to przetestować w czasie wykonywania? Nie mogłem przekazać żadnej wartości do tej funkcji ...
MartinStettner
Ponieważ jest to metoda przedłużająca? Czy jest coś innego o wyliczeniach w starszych wersjach języka C #?
BFree
Dodałem test na końcu mojego wpisu. Spróbowałem tego i to zadziałało.
BFree
@BFree: Wydaje się, że działa tylko z metodami rozszerzającymi. Wypróbowałem to z funkcją „starego stylu” (w C # 2.0) i nie udało mi się znaleźć składni, która faktycznie przekazałaby do niej jakąkolwiek wartość (o tym był mój poprzedni komentarz)
MartinStettner
15

Jeśli chcesz przekonwertować dowolne wyliczenie na jego typ bazowy (nie wszystkie wyliczenia są obsługiwane przez int), możesz użyć:

return System.Convert.ChangeType(
    enumValue,
    Enum.GetUnderlyingType(enumValue.GetType()));
Drew Noakes
źródło
5
To jedyna kuloodporna odpowiedź.
Rudey
Czy to jest przyczyną rozpakowywania boksu?
b.ben
@ b.ben yes. Convert.ChangeTypeakceptuje objectjako pierwszy argument i zwraca object.
Drew Noakes,
Tak, zgadzam się z @Rudey w tej sprawie, w każdym razie powinieneś przeprowadzić testy wydajności. Rozwiązanie BFree jest zdecydowanie najszybsze. Około osiem razy szybciej. W każdym razie nadal mówimy o kleszczach.
Dwadzieścia
10

Dlaczego musisz na nowo wynaleźć koło za pomocą metody pomocniczej? Rzucanie enumwartości na jej typ bazowy jest całkowicie legalne .

To mniej pisania, a moim zdaniem bardziej czytelne, w użyciu ...

int x = (int)DayOfWeek.Tuesday;

... zamiast czegoś w rodzaju ...

int y = Converter.ToInteger(DayOfWeek.Tuesday);
// or
int z = DayOfWeek.Tuesday.ToInteger();
LukeH
źródło
6
Chcę przekonwertować DOWOLNĄ wartość wyliczenia. To znaczy, mam wiele klas, które mają pola wyliczeniowe, które są przetwarzane przez jakiś kod w sposób ogólny. Dlatego proste rzutowanie na int nie jest właściwe.
orj
@orj, nie bardzo wiem, co masz na myśli. Czy możesz podać przykład pokazujący, jakiego zastosowania potrzebujesz?
LukeH
2
Czasami nie można bezpośrednio rzutować wyliczenia, na przykład w przypadku metody ogólnej. Ponieważ metody ogólne nie mogą być ograniczone do wyliczeń, musisz ograniczyć je do czegoś takiego jak struct, którego nie można rzutować na int. W takim przypadku możesz to zrobić za pomocą Convert.ToInt32 (enum).
Daniel T.
Podoba mi się pomysł, że metoda rozszerzenia ToInteger () ma taki sam format jak ToString (). Myślę, że mniej czytelne jest rzutowanie na int z jednej strony, gdy chcesz mieć wartość int i użyj ToString (), gdy potrzebujemy wartości ciągu, zwłaszcza gdy kod jest obok siebie.
Louise Eggleton
Ponadto użycie metody rozszerzenia może zapewnić spójność w bazie kodu. Ponieważ, jak wskazał @nawfal, istnieje kilka sposobów na uzyskanie wartości int z wyliczenia, utworzenie metody rozszerzenia i wymuszenie jej użycia oznacza, że ​​za każdym razem, gdy otrzymujesz wartość int wyliczenia, używasz tej samej metody
Louise Eggleton
4

Z mojej odpowiedzi tutaj :

Podano ejak w:

Enum e = Question.Role;

Wtedy te działają:

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);

Ostatnie dwa są po prostu brzydkie. Pierwsza powinna być bardziej czytelna, choć druga jest znacznie szybsza. A może metoda rozszerzenia jest najlepsza, najlepsza z obu światów.

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

Teraz możesz zadzwonić:

e.GetValue<int>(); //or
e.GetIntValue();
nawfal
źródło
Czy na pewno ten drugi jest szybszy? Zakładam, że zapakuje wyliczenie, a następnie rozpakuje z powrotem do int?
jojo
1
@yoyo ezmienna ma już opakowane wystąpienie rzeczywistego typu wartości, tj. wyliczenie. Kiedy piszesz Enum e = Question.Role;, jest już zapakowane. Pytanie dotyczy konwersji pudełkowanego z System.Enumpowrotem do bazowego typu int (unboxing). Więc tutaj rozpakowywanie to tylko kwestia wydajności. (int)(object)ejest bezpośrednim wywołaniem unbox; tak, powinno być szybsze niż inne podejścia. Zobacz to
nawfal
Dziękuję za wyjaśnienie. Miałem błędne wrażenie, że instancja System.Enum była wartością bez opakowania. Zobacz też - msdn.microsoft.com/en-us/library/aa691158(v=vs.71).aspx
yoyo
@yoyo hmm yes. Odpowiedni cytat z linku: od dowolnego typu wyliczenia do typu System.Enum.
nawfal
1

Przesyłanie z a System.Enumdo a intdziała dla mnie dobrze (jest również w MSDN ). Może to błąd Resharper.

jpoh
źródło
1
Jakiej wersji środowiska wykonawczego używasz?
JoshBerke
2
link pokazuje rzutowanie podtypów System.Enum, a nie System.Enumsiebie.
nawfal
Podobnym problemem był snafu cache Resharper. Zamknięcie VS i usunięcie pamięci podręcznej Resharper sprawiło, że błąd zniknął, nawet po pełnym ponownym skanowaniu.
Mir
1

Ponieważ wyliczenia są ograniczone do bajtów, sbyte, short, ushort, int, uint, long i ulong, możemy poczynić pewne założenia.

Możemy uniknąć wyjątków podczas konwersji, używając największego dostępnego kontenera. Niestety, którego pojemnika należy użyć, nie jest jasne, ponieważ ulong wysadzi w przypadku liczb ujemnych, a long dla liczb z przedziału od long.MaxValue do ulong.MaxValue. Musimy przełączać się między tymi opcjami w oparciu o podstawowy typ.

Oczywiście nadal musisz zdecydować, co zrobić, gdy wynik nie mieści się w int. Myślę, że casting jest w porządku, ale wciąż są pewne problemy:

  1. w przypadku wyliczeń opartych na typie z obszarem pola większym niż int (long i ulong) możliwe jest, że niektóre wyliczenia będą obliczane na tę samą wartość.
  2. rzutowanie liczby większej niż int.MaxValue spowoduje zgłoszenie wyjątku, jeśli jesteś w zaznaczonym regionie.

Oto moja sugestia, pozostawię to czytelnikowi, aby zdecydował, gdzie ujawnić tę funkcję; jako pomocnik lub rozszerzenie.

public int ToInt(Enum e)
{
  unchecked
  {
    if (e.GetTypeCode() == TypeCode.UInt64)
      return (int)Convert.ToUInt64(e);
    else
      return (int)Convert.ToInt64(e);
  }
}
eisenpony
źródło
-1

Nie zapominaj, że sam typ Enum zawiera kilka statycznych funkcji pomocniczych. Jeśli wszystko, co chcesz zrobić, to przekonwertować wystąpienie wyliczenia na odpowiadający mu typ liczby całkowitej, rzutowanie jest prawdopodobnie najbardziej wydajnym sposobem.

Myślę, że ReSharper narzeka, ponieważ Enum nie jest wyliczeniem żadnego określonego typu, a same wyliczenia pochodzą ze skalarnego typu wartości, a nie Enum. Jeśli potrzebujesz adaptowalnego rzutowania w ogólny sposób, powiedziałbym, że może ci to dobrze odpowiadać (zwróć uwagę, że sam typ wyliczenia jest również zawarty w generycznym:

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

Można to następnie wykorzystać w następujący sposób:

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

// ...

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

Nie mogę powiedzieć z całą pewnością, ale powyższy kod może być nawet obsługiwany przez wnioskowanie o typie C #, pozwalając na:

int myStoplightColor = EnumHelpers.Convert<int>(StopLight.Red);
jrista
źródło
1
Niestety w twoim przykładzie E może być dowolnego typu. Generics nie pozwalają mi używać Enum jako ograniczenia parametru typu. Nie mogę powiedzieć: gdzie T: Enum
Vadim
Możesz jednak użyć where T: struct. Nie jest tak rygorystyczny, jak chcesz, ale wyeliminowałby każdy typ odniesienia (co myślę, że jest to, co próbujesz zrobić).
jrista
możesz użyć: if (! typeof (E) .IsEnum) throw new ArgumentException ("E musi być typem wyliczeniowym");
diadiora
1
Nie jest to nawet zdalnie poprawne, pomocnik statyczny nie jest nawet poprawny.
Nicholi
1
Dlaczego ktoś powinien używać EnumHelpers.Convert<int, StopLight>(StopLight.Red);zamiast (int)StopLight.Read;? Również o tym mowa System.Enum.
nawfal