преобразовать перечисление в другой тип перечисления

120

У меня есть перечисление, например ' Gender' ( Male =0 , Female =1), и у меня есть другое перечисление из службы, у которой есть собственное перечисление Gender ( Male =0 , Female =1, Unknown =2)

Мой вопрос в том, как я могу написать что-то быстрое и красивое, чтобы преобразовать их перечисление в мое?

kurasa
источник
6
Во что вы хотите преобразовать «неизвестное»?
Павел Минаев
Вы можете привести перечисление к другим типам перечислений, если оба имеют одинаковые значения, см. Ideone.com/7lgvgf
Gowtham S

Ответы:

87

Использование метода расширения работает довольно аккуратно при использовании двух методов преобразования, предложенных Нейтом:

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

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

Zooba
источник
234

Учитывая 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.
Sander
47

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

Gender2 gender2 = (Gender2)((int)gender1);
Адриан Занеску
источник
3
Хотя маловероятно увидеть это `` в дикой природе '', и это очень маловероятно для полов, может существовать какое-то перечисление, поддерживаемое long(или ulong), а не, для intкоторого определены члены, которые находятся выше int.MaxValue(или ниже int.MinValue), и в этом случае приведение к intможет переполниться, и вы получите неопределенное значение перечисления, которое должно быть определено.
Rich O'Kelly
конечно. правильный способ будет (Gender2) ((вставьте здесь базовый тип) пол1), но я думаю, что приведенный выше пример дает правильную идею, поэтому я не буду его менять.
Adrian Zanescu
3
Это требует, чтобы два перечисления имели одинаковые значения в одном порядке. Хотя он решает эту конкретную проблему, он действительно хрупкий, и я бы не стал использовать его для сопоставления перечислений в целом.
sonicblis
2
ну .... ага! , Картирование нужно делать на основании чего-то. В этом случае отображение выполняется на целое значение. Для отображения базы по имени вам понадобится другой код. Для другого вида картографии что-нибудь еще. Никто не сказал, что это «для сопоставления перечислений в целом», и этот случай не существует, если вы не можете попытаться указать, что означает «сопоставление в целом»
Адриан Занеску
20

Чтобы быть внимательным, я обычно создаю пару функций, одна из которых принимает Enum 1 и возвращает Enum 2, а другая принимает Enum 2 и возвращает Enum 1. Каждая из них состоит из оператора case, отображающего входы и выходы, а case по умолчанию генерирует исключение с сообщение с жалобой на неожиданное значение.

В этом конкретном случае вы могли бы воспользоваться тем фактом, что целочисленные значения Male и Female одинаковы, но я бы избегал этого, поскольку это хакерский и может быть поврежден, если одно из перечислений изменится в будущем.

Нейт СК
источник
7
+1 Я видел, как многие разработчики отказывались использовать целочисленные значения перечислений для их преобразования, но это очень чревато ошибками. Старый школьный метод написания двух функций со временем доказал свою ценность ...
Хемант
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;

Если вы хотите охватить случай, когда перечисление справа от знака '=' имеет больше значений, чем перечисление слева, вам придется написать свой собственный метод / словарь, чтобы охватить это, как предлагали другие.

Nedcode
источник
Ваш ответ похож на вопрос !? Если да, то это не ответ, а если нет, есть аналогичный ответ выше ;).
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 02
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
    }
}
RCIX
источник
1
это не функция. ожидаемый "MyGender", а вы возвращаете "void"
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;
}
epistemophilic
источник
2

Некоторое время назад я написал набор методов расширения, которые работают для нескольких различных типов Enums. Один, в частности, работает для того, что вы пытаетесь выполнить, и обрабатывает Enums FlagsAttributeкак Enumс, так и с различными базовыми типами.

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, что может показаться неинтуитивным.

Thick_propheT
источник
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

Я знаю, что это старый вопрос и на него есть много ответов, однако я считаю, что использование оператора switch, как в принятом ответе, несколько громоздко, поэтому вот мои 2 цента:

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

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. Это вызовет исключение, если значение не поддерживается перечислением назначения (например, для значения «Неизвестно»).

Джейсон Уильямс
источник