convertir une énumération en un autre type d'énumération

120

J'ai une énumération de par exemple ` Gender` ( Male =0 , Female =1) et j'ai une autre énumération d'un service qui a sa propre énumération de genre ( Male =0 , Female =1, Unknown =2)

Ma question est de savoir comment puis-je écrire quelque chose de rapide et agréable à convertir de leur énumération au mien?

Kurasa
la source
6
En quoi voulez-vous convertir "inconnu"?
Pavel Minaev
Vous pouvez taper l'énumération dans d'autres types d'énumération lorsque les deux ont les mêmes valeurs voir ideone.com/7lgvgf
Gowtham S

Réponses:

87

L'utilisation d'une méthode d'extension fonctionne très bien, lorsque vous utilisez les deux méthodes de conversion suggérées par Nate:

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

De toute évidence, il n'est pas nécessaire d'utiliser des classes séparées si vous ne le souhaitez pas. Ma préférence est de garder les méthodes d'extension groupées en fonction des classes / structures / énumérations auxquelles elles s'appliquent.

Zooba
la source
234

Étant donné Enum1 value = ..., alors si vous entendez par nom:

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

Si vous entendez par valeur numérique, vous pouvez généralement simplement lancer:

Enum2 value2 = (Enum2)value;

(avec la distribution, vous voudrez peut-être utiliser Enum.IsDefinedpour vérifier les valeurs valides, cependant)

Marc Gravell
la source
16
C'est la meilleure réponse
Nicholas
1
Voici une version qui utilise Enum.Tryparse: Enum2 value2 = Enum.TryParse(value.ToString(), out Enum2 outValue) ? outValue : Enum2.Unknown; Cela vous permettra de gérer des valeurs d'entrée qui n'existent pas Enum2sans avoir besoin d'appeler Enum.IsDefinedou de capturer les ArgumentExceptions lancées par Enum.Parse. Notez que l'ordre des paramètres est plus ou moins inversé Enum.Parse.
Sander
47

Il suffit de convertir un en int, puis de le convertir en l'autre enum (en considérant que vous voulez que le mappage soit effectué en fonction de la valeur):

Gender2 gender2 = (Gender2)((int)gender1);
Adrian Zanescu
la source
3
Bien qu'il soit peu probable de le voir `` dans la nature '', et qu'il soit très peu probable que ce soit le cas pour les genres, il pourrait exister une énumération soutenue par un long(ou ulong) plutôt qu'un intqui a des membres définis au-dessus int.MaxValue(ou en dessous) int.MinValue), auquel cas le cast en intpourrait déborder et vous vous retrouveriez avec une valeur d'énumération non définie qui devrait être définie.
Rich O'Kelly
bien sûr. la bonne façon serait (Gender2) ((insérer le type sous-jacent ici) gender1) mais je pense que l'exemple ci-dessus donne la bonne idée, donc je ne la changerai pas.
Adrian Zanescu
3
Cela nécessite que les deux énumérations aient les mêmes valeurs dans le même ordre. Bien que cela résout ce problème spécifique, c'est vraiment fragile et je ne l'utiliserais pas pour le mappage enum en général.
sonicblis
2
bien .... duh! . La cartographie doit être faite en fonction de quelque chose. Dans ce cas, le mappage est en valeur intégrale. Pour mapper la base sur le nom, vous avez besoin d'un code différent. Pour un autre type de cartographie autre chose. Personne n'a dit que c'était "pour le mappage enum en général" et que ce cas n'existe que si vous pouvez essayer de spécifier ce que "mappage en général" signifie
Adrian Zanescu
20

Pour être minutieux, je crée normalement une paire de fonctions, une qui prend Enum 1 et renvoie Enum 2 et une autre qui prend Enum 2 et renvoie Enum 1. Chacune consiste en une instruction case qui mappe les entrées aux sorties et le cas par défaut lève une exception avec un message se plaignant d'une valeur inattendue.

Dans ce cas particulier, vous pouvez profiter du fait que les valeurs entières de Homme et Femme sont les mêmes, mais j'éviterais cela car c'est piraté et sujet à la rupture si l'une ou l'autre des énumérations change à l'avenir.

Nate CK
la source
7
+1 J'ai vu de nombreux développeurs abandonner l'envie d'utiliser la valeur entière des enums pour les convertir, mais cela est très sujet aux erreurs. La vieille méthode d'écriture de 2 fonctions a fait ses preuves au fil du temps ...
Hemant
20

Si nous avons:

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

et

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

Nous pouvons faire en toute sécurité

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

Ou même

var enumOfGender2Type = (Gender2)0;

Si vous voulez couvrir le cas où une énumération sur le côté droit du signe '=' a plus de valeurs que l'énumération sur le côté gauche - vous devrez écrire votre propre méthode / dictionnaire pour couvrir cela comme d'autres l'ont suggéré.

Nedcode
la source
Votre réponse est comme poser une question !? Si oui ce n'est pas une réponse et si non il y a une réponse similaire ci-dessus ;).
shA.t
13

Vous pouvez écrire une méthode d'extension générique simple comme celle-ci

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
la source
1
Il ne couvre pas le cas des valeurs manquantes comme suggéré dans les réponses ci-dessus. Vous devez également modifier cette méthode d'extension couvrant ce cas.
eRaisedToX
8

vous pouvez écrire une fonction simple comme celle-ci:

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
la source
1
ce n'est pas une fonction. attendu 'MyGender' et vous êtes de retour 'void'
bl4ckr0se
7

Voici une version de la méthode d'extension si quelqu'un est intéressé

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

// Usage
NewEnumType newEnum = oldEnumVar.ConvertEnum<NewEnumType>();
Justin
la source
Cela n'implique-t-il pas que les deux énumérations ont les mêmes valeurs numériques?
kuskmen
1
Non, il s'agit d'une conversion par nom par chaîne. Donc Enum.Foo (1) se traduira par Enum2.Foo (2) même si leurs valeurs numériques sont différentes.
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;
}
épistémophile
la source
2

J'ai écrit il y a quelque temps un ensemble de méthodes d'extension qui fonctionnent pour plusieurs types différents de Enums. Un en particulier fonctionne pour ce que vous essayez d'accomplir et gère les Enums avec les FlagsAttributeainsi que les Enums avec différents types sous-jacents.

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

De là, vous pouvez ajouter d'autres méthodes d'extension plus spécifiques.

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

Celui-ci changera les types de Enums comme vous essayez de le faire.

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

Soyez averti, cependant, que vous POUVEZ convertir entre n'importe lequel Enumet n'importe quel autre en Enumutilisant cette méthode, même ceux qui n'ont pas d'indicateur. Par exemple:

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

La variable turtleaura une valeur de Turtle.Blue.

Cependant, il y a sécurité à partir de Enumvaleurs non définies en utilisant cette méthode. Par exemple:

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

Dans ce cas, accesssera défini sur WriteAccess.ReadWrite, car la WriteAccess Enumvaleur maximale de l est 3.

Un autre effet secondaire du mélange de Enums avec le FlagsAttributeet ceux qui n'en ont pas est que le processus de conversion n'entraînera pas une correspondance de 1 à 1 entre leurs valeurs.

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

Dans ce cas, lettersaura une valeur de Letters.Hau lieu de Letters.D, puisque la valeur de support de Flavors.Peachest 8. De plus, une conversion de Flavors.Cherry | Flavors.Grapeen Lettersdonnerait Letters.C, ce qui peut sembler peu intuitif.

Thick_propheT
la source
2

Sur la base de la réponse de Justin ci-dessus, j'ai trouvé ceci:

    /// <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
la source
1

Je sais que c'est une vieille question et j'ai beaucoup de réponses, mais je trouve que l'utilisation d'une instruction switch comme dans la réponse acceptée est un peu lourde, alors voici mes 2 cents:

Ma méthode préférée personnelle est d'utiliser un dictionnaire, où la clé est l'énumération source et la valeur est l'énumération cible - donc dans le cas présenté sur la question, mon code ressemblerait à ceci:

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

Bien sûr, cela peut être encapsulé dans une classe statique et utilisé comme méthode d'extension:

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
la source
J'aime cette approche car vous pouvez également énumérer les deux énumérations afin de remplir le dictionnaire. (quand ils sont dans le même ordre bien sûr)
AlexS
0

Vous pouvez utiliser ToString () pour convertir la première énumération en son nom, puis Enum.Parse () pour reconvertir la chaîne en l'autre Enum. Cela lèvera une exception si la valeur n'est pas prise en charge par l'énumération de destination (c'est-à-dire pour une valeur "inconnue")

Jason Williams
la source