перетворити enum в інший тип enum


120

У мене є перелік, наприклад ' Gender' ( Male =0 , Female =1), і у мене є інший перелік від служби, яка має власну ґендерну перелік ( Male =0 , Female =1, Unknown =2)

Моє запитання - як я можу написати щось швидке і приємне, щоб перетворити їх з перерахунку на моє?


6
У що ви хочете перетворити "невідомого"?
Павло Мінаєв

Ви можете ввести enum до інших типів enum, коли обидва мають однакові значення, див. Ideone.com/7lgvgf
Gowtham S

Відповіді:


87

Використання методу розширення працює досить акуратно при використанні двох методів перетворення, запропонованих 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
    }
}

Очевидно, що немає необхідності використовувати окремі класи, якщо ви цього не хочете. Я вважаю за краще зберігати методи розширення, згруповані за класами / структурами / перерахуваннями, до яких вони застосовуються.


233

Дано Enum1 value = ..., тоді якщо ви маєте на увазі ім’я:

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

Якщо ви маєте на увазі числове значення, зазвичай ви можете просто віддати:

Enum2 value2 = (Enum2)value;

(з наголосом, ви можете скористатися Enum.IsDefinedдля перевірки дійсних значень, хоча)


16
Це краща відповідь
Микола

1
Ось версія, яка використовує Enum.Tryparse: Enum2 value2 = Enum.TryParse(value.ToString(), out Enum2 outValue) ? outValue : Enum2.Unknown; Це дозволить вам обробляти вхідні значення, які не існують, Enum2не потребуючи виклику Enum.IsDefinedабо вилучення ArgumentException, кинутого на Enum.Parse. Зауважте, що порядок параметрів більш-менш відмінений від Enum.Parse.
Сандер

47

Просто киньте один на int, а потім перекиньте його на інший перелік (враховуючи, що ви хочете, щоб картографування робилося на основі значення):

Gender2 gender2 = (Gender2)((int)gender1);

3
Хоча навряд чи можна побачити це "в дикій природі", і це вкрай мало ймовірно, що стосується гендерних питань, може існувати якась перерахунок, який підтримується long(або ulong), а не intякий має членів, визначених вище int.MaxValue(або нижче). int.MinValue), у такому випадку перелік intможе бути переповнений, і ви отримаєте невизначене значення перерахунку, яке слід визначити.
Річ О'Келлі

звичайно. правильний спосіб був би (Gender2) ((тут вставте базовий тип) gender1), але я думаю, що приклад вище дає правильну ідею, тому я не буду його змінювати.
Адріан Занеску

3
Для цього потрібно, щоб два перерахунки мали однакові значення в одному порядку. Хоча це вирішує цю конкретну проблему, це справді крихко, і я б не використовував це для перерахування перерахунків взагалі.
sonicblis

2
ну .... да! . Картографування потрібно робити на основі чогось. У цьому випадку відображення є цілісним значенням. Для відображення бази на ім’я вам потрібен інший код. Для іншого виду картування чогось іншого. Ніхто не сказав, що це "для загального картографування", і такого випадку не існує, якщо ви не можете спробувати вказати, що означає "картографування взагалі"
Адріан Занеску

20

Щоб бути ретельним, я зазвичай створюю пару функцій, одну, яка бере Enum 1 і повертає Enum 2, та іншу, яка приймає Enum 2 і повертає Enum 1. Кожна складається з оператора випадку, який відображає входи до виходів, а випадок за замовчуванням видає виняток із a повідомлення, яке скаржиться на несподіване значення.

У цьому конкретному випадку ви можете скористатися тим, що цілі значення чоловічої та жіночої статі однакові, але я б уникав цього, оскільки це хакіт і підлягає зриву, якщо будь-який перерахунок зміниться в майбутньому.


7
+1 Я бачив, як багато розробників відмовляються від використання цілого значення перерахунків для їх перетворення, але це дуже схильне до помилок. Старий шкільний метод написання 2-х функцій довів свою цінність з часом ...
Гемант

20

Якщо у нас є:

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

і

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

Ми можемо сміливо робити

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

Або навіть

var enumOfGender2Type = (Gender2)0;

Якщо ви хочете висвітлити випадок, коли enum з правого боку знака '=' має більше значень, ніж enum з лівої сторони - вам доведеться написати свій власний метод / словник, щоб прикрити те, як запропонували інші.


Ваша відповідь - це як запитання !? Якщо так, це не відповідь, а якщо ні, то аналогічна відповідь вище ;).
shA.t

13

Ви можете написати простий такий метод розширення, як цей

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
Він не охоплює випадок відсутніх значень, як запропоновано у наведених вище відповідях. Вам слід змінити цей метод розширення, що охоплює і цей випадок.
eRaisedToX

8

Ви можете написати просту функцію, наприклад:

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
це не функція. очікується "MyGender", і ви повернетесь "недійсним"
bl4ckr0se

7

Ось версія методу розширення, якщо хтось зацікавлений

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

// Usage
NewEnumType newEnum = oldEnumVar.ConvertEnum<NewEnumType>();

Чи не означає, що обидва перерахування мають однакові числові значення?
kuskmen

1
Ні, це перетворення за назвою по рядку. Так Enum.Foo (1) переведеться на Enum2.Foo (2), навіть якщо їх числові значення різні.
Джастін

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

Нещодавно я написав методи розширення набору, які працюють для декількох різних типів Enums. Зокрема, одна працює на те, що ви намагаєтеся виконати, і обробляє Enums FlagsAttributeяк і Enums з різними основними типами.

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

Звідти ви можете додати інші більш конкретні методи розширення.

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

Цей змінить типи Enums, як ви намагаєтесь зробити.

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

Попереджуйте, що ви МОЖЕТЕ перетворити будь-який Enumта будь-який інший Enumза допомогою цього методу, навіть тих, у яких немає прапорів. Наприклад:

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

Змінна turtleматиме значення Turtle.Blue.

Однак існує безпека від невизначених Enumзначень за допомогою цього методу. Наприклад:

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

У цьому випадку accessбуде встановлено значення WriteAccess.ReadWrite, оскільки значення WriteAccess Enumмає максимальне значення 3.

Ще один побічний ефект змішування Enums з тими FlagsAttributeі без них полягає в тому, що процес перетворення не призведе до відповідності 1 до 1 між їх значеннями.

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

У цьому випадку lettersбуде мати значення Letters.Hзамість Letters.D, оскільки значення резервного значення Flavors.Peachстановить 8. Також конверсія з Flavors.Cherry | Flavors.Grapeв Lettersотримає вихід Letters.C, що може здатися неінтуїтивним.


2

На підставі відповіді Джастіна вище я придумав таке:

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

Я знаю, що це старе питання, і я маю багато відповідей. Однак я вважаю, що використання заяви переключення, як у прийнятій відповіді, є дещо громіздкою, тому ось мої 2 центи:

Мій особистий улюблений метод - використовувати словник, де ключовим є джерело enum, а значення - цільовий enum - так у випадку, представленому на запитання, мій код виглядатиме так:

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

Звичайно, це може бути зафіксовано в статичний клас і використовуватись як методи розширення:

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

}

Мені подобається такий підхід, оскільки ви можете також перерахувати обидва перелічення, щоб заповнити словник. (коли вони в тому ж порядку, звичайно)
AlexS

0

Ви можете використовувати ToString () для перетворення першого переліку в його ім'я, а потім Enum.Parse () для перетворення рядка назад в інший Enum. Це призведе до виключення, якщо значення не підтримується перерахунком призначення (тобто для значення "Невідомо")

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.