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?


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.


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)


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

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.


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.


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

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

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

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

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


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

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

}

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.