przekonwertować wyliczenie na inny typ wyliczenia

120

Mam na przykład wyliczenie Gender„( Male =0 , Female =1) i mam inne wyliczenie z usługi, która ma własną wyliczenie rodzaju ( Male =0 , Female =1, Unknown =2)

Moje pytanie brzmi: jak mogę napisać coś szybko i przyjemnie, aby przekonwertować je z ich wyliczenia na moje?

kurasa
źródło
6
Na co chcesz przekonwertować „nieznane”?
Pavel Minaev
Możesz przełożyć wyliczenie na inne typy wyliczeń,
Gowtham S

Odpowiedzi:

87

Użycie metody rozszerzenia działa całkiem nieźle, gdy używa się dwóch metod konwersji sugerowanych przez Nate'a:

public static class TheirGenderExtensions
{
    public static MyGender ToMyGender(this TheirGender value)
    {
        // insert switch statement here
    }
}

public static class MyGenderExtensions
{
    public static TheirGender ToTheirGender(this MyGender value)
    {
        // insert switch statement here
    }
}

Oczywiście nie ma potrzeby używania oddzielnych klas, jeśli nie chcesz. Preferuję zachowanie metod rozszerzających pogrupowanych według klas / struktur / wyliczeń, do których mają zastosowanie.

Zooba
źródło
233

Biorąc pod uwagę Enum1 value = ..., jeśli masz na myśli imię:

Enum2 value2 = (Enum2) Enum.Parse(typeof(Enum2), value.ToString());

Jeśli masz na myśli wartość liczbową, zwykle możesz rzucić:

Enum2 value2 = (Enum2)value;

(w przypadku rzutowania możesz jednak chcieć użyć Enum.IsDefineddo sprawdzenia prawidłowych wartości)

Marc Gravell
źródło
16
To jest lepsza odpowiedź
Mikołaj
1
Oto wersja, która używa Enum.Tryparse: Enum2 value2 = Enum.TryParse(value.ToString(), out Enum2 outValue) ? outValue : Enum2.Unknown; Pozwoli ci to obsłużyć wartości wejściowe, które nie istnieją, Enum2bez konieczności wywoływania Enum.IsDefinedlub przechwytywania ArgumentExceptionwyrzuconych przez Enum.Parse. Zauważ, że kolejność parametrów jest mniej więcej odwrócona od Enum.Parse.
Sander,
47

Po prostu rzuć jeden na int, a następnie przerzuć go na drugie wyliczenie (biorąc pod uwagę, że chcesz mapować na podstawie wartości):

Gender2 gender2 = (Gender2)((int)gender1);
Adrian Zanescu
źródło
3
Chociaż jest mało prawdopodobne, aby zobaczyć to `` na wolności '' i jest bardzo mało prawdopodobne w przypadku płci, może istnieć pewne wyliczenie, które jest poparte long(lub ulong), a nie, intktóre ma członków zdefiniowanych powyżej int.MaxValue(lub poniżej) int.MinValue), w którym to przypadku rzutowanie na intmoże się przepełnić i otrzymamy niezdefiniowaną wartość wyliczenia, która powinna zostać zdefiniowana.
Rich O'Kelly,
oczywiście. prawidłowy sposób to (Gender2) ((wstaw tutaj typ bazowy) gender1), ale myślę, że powyższy przykład daje dobry pomysł, więc nie zmienię go.
Adrian Zanescu
3
Wymaga to, aby dwa wyliczenia miały te same wartości w tej samej kolejności. Chociaż rozwiązuje ten konkretny problem, jest naprawdę kruchy i nie użyłbym tego do mapowania wyliczeniowego w ogóle.
sonicblis
2
no cóż… duh! . Mapowanie musi być oparte na czymś. W tym przypadku mapowanie dotyczy wartości całkowitej. Do mapowania na podstawie nazwy potrzebujesz innego kodu. Do innego rodzaju mapowania czegoś innego. Nikt nie powiedział, że jest to „ogólne mapowanie wyliczeń”, a ten przypadek nie istnieje, chyba że możesz spróbować sprecyzować, co oznacza „ogólne mapowanie”
Adrian Zanescu,
20

Aby być dokładnym, zwykle tworzę parę funkcji, jedną, która pobiera Enum 1 i zwraca Enum 2, a drugą, która pobiera Enum 2 i zwraca Enum 1. Każda składa się z instrukcji case mapującej dane wejściowe na wyjścia, a przypadek domyślny zgłasza wyjątek z komunikat narzekający na nieoczekiwaną wartość.

W tym konkretnym przypadku możesz skorzystać z faktu, że wartości całkowite płci męskiej i żeńskiej są takie same, ale unikałbym tego, ponieważ jest to hacking i może ulec uszkodzeniu, jeśli którekolwiek wyliczenie zmieni się w przyszłości.

Nate CK
źródło
7
+1 Widziałem wielu programistów poddających się pokusie używania wartości całkowitych wyliczeń do ich konwersji, ale jest to bardzo podatne na błędy. Stara metoda pisania 2 funkcji sprawdziła się z biegiem czasu ...
Hemant,
20

Jeśli mamy:

enum Gender
{
    M = 0,
    F = 1,
    U = 2
}

i

enum Gender2
{
    Male = 0,
    Female = 1,
    Unknown = 2
}

Możemy to zrobić bezpiecznie

var gender = Gender.M;
var gender2   = (Gender2)(int)gender;

Lub nawet

var enumOfGender2Type = (Gender2)0;

Jeśli chcesz uwzględnić przypadek, w którym wyliczenie po prawej stronie znaku „=” ma więcej wartości niż wyliczenie po lewej stronie - będziesz musiał napisać własną metodę / słownik, aby to uwzględnić, tak jak sugerowali inni.

Nedcode
źródło
Twoja odpowiedź jest jak zadanie pytania !? Jeśli tak, to nie jest odpowiedź, a jeśli nie, to podobna odpowiedź znajduje się powyżej ;).
shA.t
13

Możesz napisać prostą, ogólną metodę rozszerzenia, taką jak ta

public static T ConvertTo<T>(this object value)            
    where T : struct,IConvertible
{
    var sourceType = value.GetType();
    if (!sourceType.IsEnum)
        throw new ArgumentException("Source type is not enum");
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Destination type is not enum");
    return (T)Enum.Parse(typeof(T), value.ToString());
}
Jishnu AP
źródło
1
Nie obejmuje przypadków brakujących wartości, jak sugerowano w powyższych odpowiedziach. Powinieneś zmodyfikować tę metodę rozszerzenia, obejmującą również ten przypadek.
eRaisedToX
8

możesz napisać prostą funkcję, taką jak:

public static MyGender ConvertTo(TheirGender theirGender)
{
    switch(theirGender)
    {
        case TheirGender.Male:
            break;//return male
        case TheirGender.Female:
            break;//return female
        case TheirGender.Unknown:
            break;//return whatever
    }
}
RCIX
źródło
1
to nie jest funkcja. oczekiwano „MyGender” i wrócisz jako „void”
bl4ckr0se
7

Oto wersja metody rozszerzenia, jeśli ktoś jest zainteresowany

public static TEnum ConvertEnum<TEnum >(this Enum source)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), source.ToString(), true);
    }

// Usage
NewEnumType newEnum = oldEnumVar.ConvertEnum<NewEnumType>();
Justin
źródło
Czy to nie oznacza, że ​​oba wyliczenia mają takie same wartości liczbowe?
kuskmen
1
Nie, to jest konwersja według nazwy po ciągu. Więc Enum.Foo (1) zostanie przetłumaczone na Enum2.Foo (2), mimo że ich wartości liczbowe są różne.
Justin
3
public static TEnum ConvertByName<TEnum>(this Enum source, bool ignoreCase = false) where TEnum : struct
{
    // if limited by lack of generic enum constraint
    if (!typeof(TEnum).IsEnum)
    {
        throw new InvalidOperationException("enumeration type required.");
    }

    TEnum result;
    if (!Enum.TryParse(source.ToString(), ignoreCase, out result))
    {
        throw new Exception("conversion failure.");
    }

    return result;
}
epistemofilny
źródło
2

Jakiś czas temu napisałem zestaw metod rozszerzających, które działają dla kilku różnych rodzajów Enum. Jeden w szczególności działa dla tego, co próbujesz osiągnąć i obsługuje Enums z FlagsAttributeoraz Enumz różnymi podstawowymi typami.

public static tEnum SetFlags<tEnum>(this Enum e, tEnum flags, bool set, bool typeCheck = true) where tEnum : IComparable
{
    if (typeCheck)
    {
        if (e.GetType() != flags.GetType())
            throw new ArgumentException("Argument is not the same type as this instance.", "flags");
    }

    var flagsUnderlyingType = Enum.GetUnderlyingType(typeof(tEnum));

    var firstNum = Convert.ToUInt32(e);
    var secondNum = Convert.ToUInt32(flags);

    if (set)
        firstNum |= secondNum;

    else
        firstNum &= ~secondNum;

    var newValue = (tEnum)Convert.ChangeType(firstNum, flagsUnderlyingType);

    if (!typeCheck)
    {
        var values = Enum.GetValues(typeof(tEnum));
        var lastValue = (tEnum)values.GetValue(values.Length - 1);

        if (newValue.CompareTo(lastValue) > 0)
            return lastValue;
    }

    return newValue;
}

Stamtąd możesz dodać inne, bardziej szczegółowe metody rozszerzeń.

public static tEnum AddFlags<tEnum>(this Enum e, tEnum flags) where tEnum : IComparable
{
    SetFlags(e, flags, true);
}

public static tEnum RemoveFlags<tEnum>(this Enum e, tEnum flags) where tEnum : IComparable
{
    SetFlags(e, flags, false);
}

Ten zmieni typy, Enumtak jak próbujesz to zrobić.

public static tEnum ChangeType<tEnum>(this Enum e) where tEnum : IComparable
{
    return SetFlags(e, default(tEnum), true, false);
}

Ostrzegamy jednak, że MOŻESZ konwertować między dowolnymi Enuminnymi Enumprzy użyciu tej metody, nawet tymi, które nie mają flag. Na przykład:

public enum Turtle
{
    None = 0,
    Pink,
    Green,
    Blue,
    Black,
    Yellow
}

[Flags]
public enum WriteAccess : short
{
   None = 0,
   Read = 1,
   Write = 2,
   ReadWrite = 3
}

static void Main(string[] args)
{
    WriteAccess access = WriteAccess.ReadWrite;
    Turtle turtle = access.ChangeType<Turtle>();
}

Zmienna turtlebędzie miała wartość Turtle.Blue.

Jednak Enumużycie tej metody zapewnia bezpieczeństwo przed niezdefiniowanymi wartościami. Na przykład:

static void Main(string[] args)
{
    Turtle turtle = Turtle.Yellow;
    WriteAccess access = turtle.ChangeType<WriteAccess>();
}

W tym przypadku accesszostanie ustawiony na WriteAccess.ReadWrite, ponieważWriteAccess Enum maksymalna wartość wynosi 3.

Innym efektem ubocznym mieszania Enums z tymi FlagsAttributei bez niego jest to, że proces konwersji nie spowoduje dopasowania 1 do 1 między ich wartościami.

public enum Letters
{
    None = 0,
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    H
}

[Flags]
public enum Flavors
{
    None = 0,
    Cherry = 1,
    Grape = 2,
    Orange = 4,
    Peach = 8
}

static void Main(string[] args)
{
    Flavors flavors = Flavors.Peach;
    Letters letters = flavors.ChangeType<Letters>();
}

W tym przypadku lettersbędzie miał wartość Letters.Hzamiast Letters.D, ponieważ wartość bazowa Flavors.Peachwynosi 8. Ponadto przyniosłaby konwersja z Flavors.Cherry | Flavors.Grapena , co może wydawać się nieintuicyjne.LettersLetters.C

Gruby_profil
źródło
2

Na podstawie powyższej odpowiedzi Justina wymyśliłem to:

    /// <summary>
    /// Converts Enum Value to different Enum Value (by Value Name) See https://stackoverflow.com/a/31993512/6500501.
    /// </summary>
    /// <typeparam name="TEnum">The type of the enum to convert to.</typeparam>
    /// <param name="source">The source enum to convert from.</param>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    public static TEnum ConvertTo<TEnum>(this Enum source)
    {
        try
        {
            return (TEnum) Enum.Parse(typeof(TEnum), source.ToString(), ignoreCase: true);
        }
        catch (ArgumentException aex)
        {
            throw new InvalidOperationException
            (
                $"Could not convert {source.GetType().ToString()} [{source.ToString()}] to {typeof(TEnum).ToString()}", aex
            );
        }
    }
Sam Jazz
źródło
1

Wiem, że to stare pytanie i mam wiele odpowiedzi, jednak uważam, że użycie instrukcji przełącznika, tak jak w zaakceptowanej odpowiedzi, jest nieco uciążliwe, więc oto moje 2 centy:

Moją ulubioną metodą jest użycie słownika, w którym kluczem jest wyliczenie źródłowe, a wartością jest wyliczenie docelowe - więc w przypadku przedstawionym na pytaniu mój kod wyglądałby tak:

var genderTranslator = new Dictionary<TheirGender, MyGender>();
genderTranslator.Add(TheirGender.Male, MyGender.Male);
genderTranslator.Add(TheirGender.Female, MyGender.Female);
genderTranslator.Add(TheirGender.Unknown, MyGender.Unknown);

// translate their to mine    
var myValue = genderTranslator[TheirValue];

// translate mine to their
var TheirValue = genderTranslator .FirstOrDefault(x => x.Value == myValue).Key;;

Oczywiście można to opakować w klasę statyczną i wykorzystać jako metody rozszerzające:

public static class EnumTranslator
{

    private static Dictionary<TheirGender, MyGender> GenderTranslator = InitializeGenderTranslator();

    private static Dictionary<TheirGender, MyGender> InitializeGenderTranslator()
    {
        var translator = new Dictionary<TheirGender, MyGender>();
        translator.Add(TheirGender.Male, MyGender.Male);
        translator.Add(TheirGender.Female, MyGender.Female);
        translator.Add(TheirGender.Unknown, MyGender.Unknown);
        return translator;
    }

    public static MyGender Translate(this TheirGender theirValue)
    {
        return GenderTranslator[theirValue];
    }

    public static TheirGender Translate(this MyGender myValue)
    {
        return GenderTranslator.FirstOrDefault(x => x.Value == myValue).Key;
    }

}
Zohar Peled
źródło
Podoba mi się to podejście, ponieważ możesz również wyliczyć oba wyliczenia, aby wypełnić słownik. (oczywiście gdy są w tej samej kolejności)
AlexS
0

Możesz użyć ToString (), aby przekonwertować pierwsze wyliczenie na jego nazwę, a następnie Enum.Parse (), aby przekonwertować ciąg z powrotem do drugiego Enum. Spowoduje to zgłoszenie wyjątku, jeśli wartość nie jest obsługiwana przez docelowe wyliczenie (np. Dla wartości „Nieznane”)

Jason Williams
źródło